Skip to content
Merged
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
64 changes: 64 additions & 0 deletions CVTemplates.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/bin/bash
set -e

echo "Cloning SRL-Development repo to extract fonts..."

# Create fonts directory if it doesn't exist
mkdir -p "src/main/resources/fonts"

# Create UI images directory if it doesn't exist
mkdir -p "src/main/resources/images/ui"

# Clone the repo into a temp folder
git clone --depth 1 https://github.com/Villavu/SRL-Development.git temp_srl_fonts

# Copy font files
cp -r temp_srl_fonts/fonts/. src/main/resources/fonts/

# Clean up
rm -rf temp_srl_fonts

echo "Fonts copied to src/main/resources/fonts/"

# Download UI images from OSBC repo
UI_DIR="src/main/resources/images/ui"

echo "Downloading UI images..."

BASE_URL="https://raw.githubusercontent.com/kelltom/OS-Bot-COLOR/main/src/images/bot/ui_templates"
curl -o "$UI_DIR/chat.png" "$BASE_URL/chat.png"
curl -o "$UI_DIR/inv.png" "$BASE_URL/inv.png"
curl -o "$UI_DIR/minimap.png" "$BASE_URL/minimap.png"
curl -o "$UI_DIR/minimap_fixed.png" "$BASE_URL/minimap_fixed.png"

# Download Mouse Click images
MOUSE_DIR="src/main/resources/images/mouse_clicks"

mkdir -p "$MOUSE_DIR"

echo "Downloading Mouse Click images to $MOUSE_DIR..."

MOUSE_URL="https://raw.githubusercontent.com/kelltom/OS-Bot-COLOR/main/src/images/bot/mouse_clicks"
curl -o "$MOUSE_DIR/red_1.png" "$MOUSE_URL/red_1.png"
curl -o "$MOUSE_DIR/red_2.png" "$MOUSE_URL/red_2.png"
curl -o "$MOUSE_DIR/red_3.png" "$MOUSE_URL/red_3.png"
curl -o "$MOUSE_DIR/red_4.png" "$MOUSE_URL/red_4.png"

echo "Mouse clicks downloaded."

echo "UI images downloaded to $UI_DIR"

# Generate .index files for fonts
FONT_DIR="src/main/resources/fonts"

echo "Generating index files in $FONT_DIR..."

for font_path in "$FONT_DIR"/*/; do
font_name=$(basename "$font_path")
echo "Writing index for \"$font_name\"..."
# List all .bmp filenames (basename only) into the index file
find "$font_path" -maxdepth 1 -name "*.bmp" -printf "%f\n" \
> "${font_path}${font_name}.index"
done

echo "Done generating index files."
Empty file modified gradlew
100644 → 100755
Empty file.
5 changes: 5 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
rootProject.name = "chromascape"

// Resolve canonical project dir to handle systems where /home is a symlink (e.g. Fedora /var/home)
// e.g. Fedora Silverblue, Kinoite, Bluefin, Bazzite, etc.
// Without this, Spotless compares /home/... against /var/home/... and fails.
rootProject.projectDir = rootProject.projectDir.canonicalFile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -35,8 +34,8 @@ public class ProfileManager {
/** Path to the current user's home directory. */
private final String userHome = System.getProperty("user.home");

/** RuneLite profile directory (typically ~/.runelite/profiles2). */
private final Path profileDir = Paths.get(userHome, ".runelite/profiles2");
/** RuneLite profile directory. On Linux with native Bolt, uses the Bolt data path. */
private final Path profileDir = resolveProfileDir();

/** List of all loaded RuneLite profiles from profiles.json. */
private List<Profile> profiles = null;
Expand All @@ -52,6 +51,44 @@ public ProfileManager() {
mapper = new ObjectMapper();
}

/**
* Resolves the RuneLite profile directory for the current operating system.
*
* <p>On Windows, returns the standard {@code %APPDATA%\Local\RuneLite\profiles2} path.
*
* <p>On Linux, checks for a native Bolt installation first ({@code
* ~/.var/app/com.adamcake.Bolt/data/bolt-launcher/.runelite/profiles2}), falling back to the
* standard {@code ~/.runelite/profiles2} path if Bolt is not detected.
*
* <p>On macOS, returns {@code ~/Library/Application Support/RuneLite/profiles2}.
*
* @return The resolved profile directory path
*/
private Path resolveProfileDir() {
String home = System.getProperty("user.home");
String os = System.getProperty("os.name").toLowerCase();

if (os.contains("win")) {
return Path.of(home, "AppData", "Local", "RuneLite", "profiles2");
}

if (os.contains("linux")) {
Path boltPath =
Path.of(home, ".var/app/com.adamcake.Bolt/data/bolt-launcher/.runelite/profiles2");
if (Files.exists(boltPath.getParent())) {
logger.info("ProfileManager: using Bolt RuneLite path: {}", boltPath);
return boltPath;
}
}

if (os.contains("mac")) {
return Path.of(home, "Library/Application Support/RuneLite/profiles2");
}

// Fallback (Linux standard or unknown OS)
return Path.of(home, ".runelite/profiles2");
}

/**
* Ensures that the ChromaScape profile exists in the RuneLite configuration.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,56 @@
package com.chromascape.utils.core.screen.window;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
* A class whose sole responsibility is to provide a native Linux implementation to return the
* process ID of RuneLite.
* Utility class for locating and identifying a specific native process (e.g., "RuneLite") on the
* Linux operating system by scanning {@code /proc/[pid]/cmdline} entries.
*
* <p>Assumes a shared PID namespace — {@code /proc} must be visible.
*/
public class LinuxProcessManager implements ProcessManager {

private static final Logger logger = LogManager.getLogger(LinuxProcessManager.class);
private static final String RUNELITE_MAIN_CLASS = "net.runelite.client.RuneLite";

/**
* To provide a linux native way of grabbing and returning the Process ID of RuneLite. This is to
* be used by RemoteInput.
* Returns the Process ID of RuneLite. Scans {@code /proc/[pid]/cmdline} entries for the RuneLite
* main class ({@code net.runelite.client.RuneLite}).
*
* @return An integer Process ID
* @return The integer process ID of RuneLite, or {@code -1} if not found
*/
@Override
public int getPid() {
return -1;
Path proc = Paths.get("/proc");
try (DirectoryStream<Path> stream = Files.newDirectoryStream(proc, "[0-9]*")) {
for (Path pidDir : stream) {
Path cmdlinePath = pidDir.resolve("cmdline");
try {
byte[] bytes = Files.readAllBytes(cmdlinePath);
// /proc/[pid]/cmdline is null-byte delimited — replace for string matching
String cmdline = new String(bytes, StandardCharsets.UTF_8).replace('\0', ' ').trim();
if (cmdline.contains(RUNELITE_MAIN_CLASS)) {
return Integer.parseInt(pidDir.getFileName().toString());
}
} catch (NoSuchFileException ignored) {
// Process exited between directory listing and read — skip silently
} catch (NumberFormatException ignored) {
// pidDir name is not a valid integer — skip
} catch (IOException e) {
logger.debug("Failed to read cmdline for {}: {}", pidDir, e.getMessage());
}
}
} catch (IOException e) {
logger.error("Failed to iterate /proc: {}", e.getMessage());
}
return -1; // May be -1 if not found
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public static ProcessManager getProcessManager() {
if (os.contains("mac")) {
return new MacProcessManager();
}
return new LinuxProcessManager();
if (os.contains("linux")) {
return new LinuxProcessManager();
}
throw new UnsupportedOperationException("Unsupported OS: " + System.getProperty("os.name"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ public void start() {
instance.run();
} finally {
StatisticsManager.stop();
// Clear the interrupted flag so the blocking WebSocket send in broadcast()
// can acquire its semaphore. The thread was interrupted by stop() to end
// the script; the flag is no longer meaningful at this point.
Thread.interrupted();
stateHandler.broadcast(false);
}
});
Expand Down
170 changes: 137 additions & 33 deletions third-party/DEV_README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,141 @@
# Preamble
[Brandon-T's RemoteInput](https://github.com/Brandon-T/RemoteInput) (RI) is a C++ based utility that provides IO to a Java application.
It serves as a bridge between the automation framework (this project) and the client.
RemoteInput functions by injecting a part of itself into the target and then using commands in AWT semantics to simulate IO.
This approach as opposed to OS level ones allows the user to keep using their mouse and keyboard and bypasses
the `LLMHF_INJECTED` flag that RuneLite checks for.
RemoteInput is currently being used in [SRL](https://github.com/Villavu/SRL-Development),
we have taken a great deal of inspiration from their integration of RI.

The following guide explains how to build the binary for yourself, allowing you to audit the code beforehand.
If you want to use pre-compiled binaries, they are available from
[Brandon-T's reflection Auto-Build GitHub Actions pipeline](https://github.com/Brandon-T/Reflection/releases/tag/autobuild).
Alternatively, they are pre-packaged with ChromaScape (currently only Windows) in the `third-party/RemoteInput/precompiled` folder.

[Brandon-T's RemoteInput](https://github.com/Brandon-T/RemoteInput) (RI) is a C++ utility that provides I/O bridging between this automation framework and the RuneLite client. It works by injecting a component of itself into the target process and issuing commands in AWT semantics to simulate input events. This approach—as opposed to OS-level input methods—allows the user to retain normal use of their mouse and keyboard, and bypasses the `LLMHF_INJECTED` flag that RuneLite checks for.

RemoteInput is also used in [SRL](https://github.com/Villavu/SRL-Development), from which this project has drawn significant integration inspiration.

The following guide explains how to build the binary from source, enabling you to audit the code prior to use. Pre-compiled binaries are available from [Brandon-T's Reflection Auto-Build pipeline](https://github.com/Brandon-T/Reflection/releases/tag/autobuild), and are also bundled with ChromaScape (currently Windows only) under `third-party/RemoteInput/precompiled/`.

---

# Windows Instructions

1. Install [MSYS2](https://www.msys2.org/)
2. In an MSYS2 terminal, install the following dependencies:
- Windows x32:
> `pacman -S mingw-w64-i686-gcc mingw-w64-i686-clang mingw-w64-i686-python mingw-w64-i686-cmake make`
- Windows x64:
> `pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-clang mingw-w64-x86_64-python mingw-w64-x86_64-cmake make`
3. In an MSYS2 MinGW terminal, navigate to the RemoteInput directory:
> e.g., `cd /c/Users/YourName/repos/ChromaScape/third-party/RemoteInput`
4. To build the binary, execute the following in the `RemoteInput` project's root folder, same level as CMakeLists.txt,
in an MSYS2 MinGW terminal:
```
# Set flags: "-m64" for 64-bit or "-m32" for 32-bit
cmake -S . -B cmake-build-release -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DOTHER_LINKER_FLAGS="-m64"

# Build
cmake --build cmake-build-release --target all -j 4
```
5. The binary will be located as: `third-party/RemoteInput/cmake-build-release/libRemoteInput.dll`
6. ChromaScape checks for a compiled binary before resorting to a provided pre-compiled one,
however you may safely delete the pre-compiled folder and any binaries within:
> `third-party/RemoteInput/precompiled`
1. Install [MSYS2](https://www.msys2.org/).

2. In an MSYS2 terminal, install the required dependencies for your target architecture:

- **Windows x32:**
```sh
pacman -S mingw-w64-i686-gcc mingw-w64-i686-clang mingw-w64-i686-python mingw-w64-i686-cmake make
```
- **Windows x64:**
```sh
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-clang mingw-w64-x86_64-python mingw-w64-x86_64-cmake make
```

3. Open an MSYS2 MinGW terminal and navigate to the `RemoteInput` directory:
```sh
cd /c/Users/YourName/repos/ChromaScape/third-party/RemoteInput
```

4. From the `RemoteInput` project root (same level as `CMakeLists.txt`), run the following to configure and build the binary:
```sh
# Set flags: "-m64" for 64-bit or "-m32" for 32-bit
cmake -S . -B cmake-build-release -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DOTHER_LINKER_FLAGS="-m64"

# Build
cmake --build cmake-build-release --target all -j 4
```

5. The compiled binary will be output to:
```
third-party/RemoteInput/cmake-build-release/libRemoteInput.dll
```

6. ChromaScape will prefer a locally compiled binary over the bundled pre-compiled one. If desired, the pre-compiled directory and its contents can be safely removed:
```
third-party/RemoteInput/precompiled/
```

---

# Linux Instructions

1. Install the following dependencies for your distribution. Required packages:
[make](https://www.gnu.org/software/make/make.html),
[cmake](https://cmake.org/),
[python3-dev](https://packages.debian.org/de/sid/python3-dev),
[libgl-dev](https://packages.debian.org/de/sid/libgl-dev).

- **Fedora x64:**
```sh
sudo dnf install make cmake libGL-devel python3-devel
```
- **Ubuntu x64:**
```sh
sudo apt install make cmake libgl-dev python3-dev
```
- **Arch x64:**
```sh
sudo pacman -S make cmake mesa python
```

2. Navigate to the `RemoteInput` directory:
```sh
cd /path/to/ChromaScape/third-party/RemoteInput
```

3. From the `RemoteInput` project root (same level as `CMakeLists.txt`), run the following to configure and build the binary:
```sh
# Set flags: "-m64" for 64-bit or "-m32" for 32-bit
cmake -S . -B cmake-build-release -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DOTHER_LINKER_FLAGS="-m64"

# Build
cmake --build cmake-build-release --target all -j 4
```

4. The compiled binary will be output to:
```
third-party/RemoteInput/cmake-build-release/libRemoteInput.so
```

5. ChromaScape will prefer a locally compiled binary over the bundled pre-compiled one. If desired, the pre-compiled directory and its contents can be safely removed:
```
third-party/RemoteInput/precompiled/
```

---

## Additional Notes

### Attaching to the RuneLite Window

If ChromaScape is unable to attach to the RuneLite window, verify that the process is not running under `seccomp` restrictions:

```sh
cat /proc/<pid>/status | grep -E seccomp
```

Expected output indicating no restrictions:

```
Seccomp: 0
Seccomp_filters: 0
```

If the output differs, RuneLite is likely running inside a `bwrap` sandbox, which will prevent ChromaScape from attaching. Exiting the sandbox environment is required before proceeding.

### Granting ptrace Permissions

If `seccomp` is not the issue but errors persist, the target executable may need explicit `ptrace` capabilities granted:

```sh
sudo setcap cap_sys_ptrace=eip /path/to/executable
```

**Example:**
```sh
sudo setcap cap_sys_ptrace=eip /usr/lib/jvm/java-21-openjdk-amd64/bin/java
```

For additional context, see the [RemoteInput repository](https://github.com/Brandon-T/RemoteInput).

### OpenGL Requirement

ChromaScape requires RuneLite to be running with OpenGL enabled. This is commonly overlooked when running in a virtual machine, where OpenGL may be disabled by default.

To launch RuneLite with OpenGL explicitly enabled:

```sh
java -Dsun.java2d.opengl=true -jar /path/to/RuneLite.jar
```
Loading