diff --git a/base/src/main/kotlin/de/staropensource/engine/base/utility/Process.kt b/base/src/main/kotlin/de/staropensource/engine/base/utility/Process.kt
new file mode 100644
index 0000000..c223ccf
--- /dev/null
+++ b/base/src/main/kotlin/de/staropensource/engine/base/utility/Process.kt
@@ -0,0 +1,333 @@
+/*
+ * STAROPENSOURCE ENGINE SOURCE FILE
+ * Copyright (c) 2024 The StarOpenSource Engine Authors
+ * Licensed under the GNU General Public License v3.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.staropensource.engine.base.utility
+
+import de.staropensource.engine.base.Engine.Companion.logger
+import de.staropensource.engine.base.exception.io.IOAccessException
+import de.staropensource.engine.base.implementable.stream.ReadStream
+import de.staropensource.engine.base.implementable.stream.Stream
+import de.staropensource.engine.base.implementable.stream.WriteStream
+import de.staropensource.engine.base.implementation.stream.JavaReadStream
+import de.staropensource.engine.base.implementation.stream.JavaWriteStream
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+
+/**
+ * Provides a simplified way of
+ * starting and managing processes.
+ *
+ * @since v1-alpha10
+ */
+@Suppress("Unused")
+class Process {
+ // -----> Metadata
+ /**
+ * Contains an instance of Java's
+ * [java.lang.Process] class.
+ *
+ * @since v1-alpha10
+ */
+ private val process: java.lang.Process
+
+ /**
+ * Contains the executable this
+ * [Process] is running.
+ *
+ * @since v1-alpha10
+ */
+ val executable: FileAccess
+
+ /**
+ * Contains an array of initial command
+ * line arguments of this [Process].
+ *
+ * @since v1-alpha10
+ */
+ val arguments: Array
+
+ /**
+ * Contains the initial working
+ * directory of this [Process].
+ *
+ * @since v1-alpha10
+ */
+ val initialWorkingDirectory: FileAccess
+
+ /**
+ * Contains if this [Process] was launched
+ * without inheriting any environment
+ * variables from this process.
+ *
+ * @since v1-alpha10
+ */
+ val launchedWithFreshEnvironment: Boolean
+
+ /**
+ * Contains all registered exit events.
+ *
+ * These are triggered when this
+ * [Process] is terminated.
+ *
+ * @since v1-alpha10
+ */
+ val exitEvents: MutableList Unit>
+
+ /**
+ * Contains the standard input
+ * [Stream] of this [Process].
+ *
+ * @since v1-alpha10
+ */
+ val standardInput: WriteStream
+
+ /**
+ * Contains the standard output
+ * [Stream] of this [Process].
+ *
+ * @since v1-alpha10
+ */
+ val standardOutput: ReadStream
+
+ /**
+ * Contains the standard error
+ * [Stream] of this [Process].
+ *
+ * @since v1-alpha10
+ */
+ val standardError: ReadStream
+
+
+ // -----> Constructors
+ /**
+ * Creates a new process.
+ *
+ * @param executable executable to invoke
+ * @param arguments command line arguments passed to the executable
+ * @param workingDirectory directory to start the process in
+ * @param freshEnvironment `true` if the process starts with a fresh environment or `false` if it inherits the environment variables from this process
+ * @param environmentVariables a map of environment variables to set
+ * @param exitEvents exit events to register. More can be registered using [exitEvents]
+ * @param inputStreams array of [Stream]s to connect to the [Process.standardInput]. The [Stream.pipes] list of all [Stream]s passed will be updated to contain this [Process]' [Process.standardInput]
+ * @param outputStreams array of [Stream]s to connect to the [Process.standardOutput]. The [Stream.pipes] list of [Process.standardOutput] will be updated to contain all passed [Stream]s
+ * @param errorStreams array of [Stream]s to connect to the [Process.standardError]. The [Stream.pipes] list of [Process.standardError] will be updated to contain all passed [Stream]s
+ * @param autoWatchStreams whether to automatically invoke [Stream.watch] on the [Process.standardOutput] and [Process.standardError] [Stream]s
+ * @throws IOAccessException on IO Error
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ constructor(
+ executable: FileAccess,
+ arguments: Array = arrayOf(),
+ workingDirectory: FileAccess? = null,
+ freshEnvironment: Boolean = false,
+ environmentVariables: Map = mapOf(),
+ exitEvents: Array Unit> = arrayOf(),
+ inputStreams: Array = arrayOf(),
+ outputStreams: Array = arrayOf(),
+ errorStreams: Array = arrayOf(),
+ autoWatchStreams: Boolean = true,
+ ) {
+ try {
+ val builder: ProcessBuilder = ProcessBuilder()
+
+ // Set properties
+ this.executable = executable
+ this.arguments = arguments
+ this.initialWorkingDirectory = workingDirectory ?: FileAccess(System.getProperty("user.dir"))
+ this.launchedWithFreshEnvironment = freshEnvironment
+ this.exitEvents = exitEvents.toMutableList()
+
+ // Reset environment
+ if (freshEnvironment)
+ builder.environment().clear()
+
+ // Pass arguments
+ builder
+ .command(listOf(executable.toStringRaw()).plus(arguments))
+ .directory(workingDirectory?.file)
+ .environment().putAll(environmentVariables)
+
+ // Start process
+ process = builder.start()
+ logger.diag("Started process ${getPid()}")
+
+ // Set streams
+ this.standardError = StandardOutputErrorStream(this, process.errorStream)
+ this.standardOutput = StandardOutputErrorStream(this, process.inputStream)
+ this.standardInput = StandardInputStream(this, process.outputStream)
+
+ // Attach process exit handling
+ process.onExit().thenAccept {
+ logger.diag("Process ${getPid()} died")
+
+ // Invoke all exit events
+ for (exitEvent: UByte.() -> Unit in exitEvents)
+ exitEvent.invoke(process.exitValue().toUByte())
+
+ // Close streams
+ standardInput.close()
+ standardOutput.close()
+ standardError.close()
+ }
+
+ // Connect streams
+ inputStreams.forEach { it.pipes.add(standardInput) }
+ standardOutput.pipes.addAll(outputStreams)
+ standardError.pipes.addAll(errorStreams)
+
+ // Automatically watch
+ if (autoWatchStreams) {
+ standardOutput.watch()
+ standardError.watch()
+ }
+ } catch (exception: IOException) {
+ throw IOAccessException("Unable to spawn new process", exception)
+ }
+ }
+
+
+ // -----> Lifecycle
+ /**
+ * Kills this process.
+ *
+ * Does nothing if already terminated.
+ *
+ * @return this instance
+ * @since v1-alpha10
+ */
+ fun kill(): Process {
+ logger.diag("Killing process ${getPid()}")
+ process.destroyForcibly()
+ return this
+ }
+
+
+ // -----> Getters & setters
+ /**
+ * Returns if this process
+ * is still running.
+ *
+ * @return still running?
+ * @since v1-alpha10
+ */
+ fun isAlive(): Boolean = process.isAlive
+
+ /**
+ * Returns the PID of this [Process]
+ * set by the operating system.
+ *
+ * If this [Process] is already dead,
+ * then it's past PID will be returned.
+ *
+ * @return process identifier
+ * @since v1-alpha10
+ */
+ fun getPid(): Long = process.pid()
+
+ /**
+ * Returns the code with which
+ * this [Process] exited.
+ *
+ * @return exit code or `null` if alive
+ * @since v1-alpha10
+ */
+ fun getExitCode(): UByte? {
+ return try {
+ process.exitValue().toUByte()
+ } catch (exception: IllegalThreadStateException) {
+ null
+ }
+ }
+
+
+ // -----> Miscellaneous
+ /**
+ * Waits until this [Process] has exited.
+ *
+ * @param maxTime amount of time to wait in milliseconds
+ * @return this instance
+ * @since v1-alpha10
+ */
+ fun waitForExit(maxTime: ULong = 0UL): Process {
+ val maxTimeReal: Long = System.currentTimeMillis().plus(maxTime.toLong())
+
+ while (maxTime == 0UL || System.currentTimeMillis() < maxTimeReal)
+ if (!isAlive())
+ break
+
+ return this
+ }
+
+
+ // -----> Inner classes
+ /**
+ * Used for the [standardInput]
+ * stream of a [Process].
+ *
+ * @since v1-alpha10
+ */
+ private class StandardInputStream(
+ val process: Process,
+ stream: OutputStream,
+ ) : JavaWriteStream(
+ stream,
+ autoFlushAfter = 100UL
+ ) {
+ // -----> Closure
+ override fun close() {
+ if (!process.isAlive())
+ super.close()
+ }
+
+ // -----> Writing
+ // Assist in flushing
+ override fun writeByte(byte: Byte): Stream {
+ super.writeByte(byte)
+ if (byte.toInt() == 0x0A)
+ flush()
+ return this
+ }
+
+ override fun writeBytes(bytes: ByteArray): Stream {
+ super.writeBytes(bytes)
+ if (bytes.contains((0x0A).toByte()))
+ flush()
+ return this
+ }
+ }
+
+ /**
+ * Used for the [standardOutput]
+ * and [standardError] streams
+ * of a [Process].
+ *
+ * @since v1-alpha10
+ */
+ private class StandardOutputErrorStream(
+ val process: Process,
+ stream: InputStream,
+ ) : JavaReadStream(stream) {
+ override fun close() {
+ if (!process.isAlive())
+ super.close()
+ }
+ }
+}