diff --git a/.gitignore b/.gitignore
index 87ddbdd..0a46005 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,8 +62,8 @@ Thumbs.db
output/*
!output/.gitkeep
-# chromascape runtime cache
-.chromascape/
+# chromascape secrets for individual developers
+secrets.properties
# custom user scripts
/src/main/java/com/chromascape/scripts/*/
diff --git a/CVTemplates.sh b/CVTemplates.sh
deleted file mode 100644
index 764c4ef..0000000
--- a/CVTemplates.sh
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/bin/bash
-
-echo "Cloning SRL-Development repo to extract fonts..."
-
-# Create fonts directory if it doesn't exist
-if [ ! -d "src/main/resources/fonts" ]; then
- mkdir -p "src/main/resources/fonts"
-fi
-
-# Create UI images directory if it doesn't exist
-if [ ! -d "src/main/resources/images/ui" ]; then
- mkdir -p "src/main/resources/images/ui"
-fi
-
-# 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/ 2>/dev/null || true
-
-# 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..."
-
-curl -o "$UI_DIR/chat.png" https://raw.githubusercontent.com/kelltom/OS-Bot-COLOR/main/src/images/bot/ui_templates/chat.png
-curl -o "$UI_DIR/inv.png" https://raw.githubusercontent.com/kelltom/OS-Bot-COLOR/main/src/images/bot/ui_templates/inv.png
-curl -o "$UI_DIR/minimap.png" https://raw.githubusercontent.com/kelltom/OS-Bot-COLOR/main/src/images/bot/ui_templates/minimap.png
-curl -o "$UI_DIR/minimap_fixed.png" https://raw.githubusercontent.com/kelltom/OS-Bot-COLOR/main/src/images/bot/ui_templates/minimap_fixed.png
-
-# Download compass degree images from RuneDark repo
-echo "Cloning RuneDark repo to extract compass degrees..."
-
-# Create compass directory if it doesn't exist
-if [ ! -d "src/main/resources/images/ui/compass_degrees" ]; then
- mkdir -p "src/main/resources/images/ui/compass_degrees"
-fi
-
-# Clone the repo into a temp folder
-git clone --depth 1 https://github.com/cemenenkoff/runedark-public.git temp_runedark
-
-# Copy compass degree images
-cp -r temp_runedark/src/img/bot/ui_templates/compass_degrees/* src/main/resources/images/ui/compass_degrees/ 2>/dev/null || true
-
-# Clean up
-rm -rf temp_runedark
-
-echo "Compass degree images copied to src/main/resources/images/ui/compass_degrees/"
-echo "UI images downloaded to $UI_DIR"
-
-# Now generate .index files for fonts
-FONT_DIR="src/main/resources/fonts"
-
-echo "Generating index files in $FONT_DIR..."
-
-# Loop through each directory in fonts
-for font_path in "$FONT_DIR"/*; do
- if [ -d "$font_path" ]; then
- font_name=$(basename "$font_path")
- echo "Writing index for \"$font_name\"..."
-
- # Create index file with list of .bmp files
- find "$font_path" -name "*.bmp" -exec basename {} \; > "$font_path/$font_name.index"
- fi
-done
-
-echo "Done generating index files."
diff --git a/config/checkstyle/checkstyle-suppressions.xml b/config/checkstyle/checkstyle-suppressions.xml
index 81bb9c2..781e836 100644
--- a/config/checkstyle/checkstyle-suppressions.xml
+++ b/config/checkstyle/checkstyle-suppressions.xml
@@ -12,4 +12,5 @@
This is extremely useful if you aren't actively babysitting your bot or in the case of + * reaching a specified XP/GP goal. It's my personal advice to the reader for you to inform yourself + * upon catastrophic failure, promptly. + */ +public class DiscordNotification { + + private static final Logger logger = LoggerFactory.getLogger(DiscordNotification.class); + private static String webhookUrl; + + static { + try (InputStream input = new FileInputStream("secrets.properties")) { + Properties prop = new Properties(); + prop.load(input); + webhookUrl = prop.getProperty("discord.webhook.url"); + } catch (IOException ex) { + logger.info("Could not find secrets.properties in the project root."); + } + } + + /** + * Sends a user specified message to a Discord WebHook endpoint. Sets up a post request and + * expects a 204 response code for success. + * + * @param message User specified String to send to the endpoint. + */ + public static void send(String message) { + if (webhookUrl == null || webhookUrl.isEmpty()) { + return; + } + + String sanitizedMessage = message.replace("\"", "\\\"").replace("\n", "\\n"); + String jsonPayload = "{\"content\": \"" + sanitizedMessage + "\"}"; + + try { + URL url = new URL(webhookUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setDoOutput(true); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("User-Agent", "Java-Discord-Webhook"); + + try (OutputStream os = conn.getOutputStream()) { + os.write(jsonPayload.getBytes(StandardCharsets.UTF_8)); + } + + // 204 means Success (No Content) + if (conn.getResponseCode() != 204) { + logger.error("Failed to send. Response code: {}", conn.getResponseCode()); + } + + conn.disconnect(); + } catch (Exception e) { + logger.error("Error sending Discord notification: {}", e.getMessage()); + } + } +} diff --git a/src/main/java/com/chromascape/base/BaseScript.java b/src/main/java/com/chromascape/base/BaseScript.java index a3e3dee..7341b11 100644 --- a/src/main/java/com/chromascape/base/BaseScript.java +++ b/src/main/java/com/chromascape/base/BaseScript.java @@ -1,7 +1,6 @@ package com.chromascape.base; import com.chromascape.controller.Controller; -import com.chromascape.utils.core.runtime.HotkeyListener; import com.chromascape.utils.core.runtime.ScriptStoppedException; import com.chromascape.utils.core.state.BotState; import com.chromascape.utils.core.state.StateManager; @@ -13,38 +12,35 @@ /** * Abstract base class representing a generic automation script with lifecycle management. * - *
Provides a timed execution framework where the script runs cycles until a specified duration - * elapses or the script is stopped externally. + *
Provides a timed execution framework where the script runs cycles until the script is stopped + * externally. * - *
Manages the underlying Controller instance and logging. Subclasses should override {@link - * #cycle()} to define the script's main logic. + *
Manages the underlying Controller instance. Subclasses should override {@link #cycle()} to + * define the script's main logic. */ public abstract class BaseScript { private final Controller controller; private static final Logger logger = LogManager.getLogger(BaseScript.class); - private final HotkeyListener hotkeyListener; private volatile boolean running = true; private Thread scriptThread; /** Constructs a BaseScript. */ public BaseScript() { controller = new Controller(); - hotkeyListener = new HotkeyListener(this); } /** * Runs the script lifecycle. * *
Initializes the controller, logs start and stop events, then continuously invokes the {@link - * #cycle()} method until the specified duration elapses or the script is stopped. Checks for - * thread interruption and stops gracefully if detected. + * #cycle()} method until the script is stopped. Checks for thread interruption and stops + * gracefully if detected. * *
This method blocks until completion. */ public final void run() { scriptThread = Thread.currentThread(); controller.init(); - hotkeyListener.start(); StatisticsManager.reset(); try { @@ -61,7 +57,7 @@ public final void run() { break; } catch (Exception e) { StateManager.setState(BotState.ERROR); - logger.error("Exception in cycle: {}", e.getMessage()); + logger.error("Exception in cycle: {}, {}", e.getMessage(), e.getStackTrace()); break; } } @@ -84,7 +80,6 @@ public void stop() { } logger.info("Stop requested"); running = false; - hotkeyListener.stop(); // Interrupt the script thread instead of throwing exception if (scriptThread != null) { diff --git a/src/main/java/com/chromascape/scripts/DemoAgilityScript.java b/src/main/java/com/chromascape/scripts/DemoAgilityScript.java index de86b05..dd468fc 100644 --- a/src/main/java/com/chromascape/scripts/DemoAgilityScript.java +++ b/src/main/java/com/chromascape/scripts/DemoAgilityScript.java @@ -1,20 +1,18 @@ package com.chromascape.scripts; +import com.chromascape.api.DiscordNotification; import com.chromascape.base.BaseScript; +import com.chromascape.utils.actions.Minimap; import com.chromascape.utils.actions.MovingObject; import com.chromascape.utils.actions.PointSelector; -import com.chromascape.utils.core.screen.colour.ColourInstances; import com.chromascape.utils.core.screen.colour.ColourObj; import com.chromascape.utils.core.screen.topology.ColourContours; -import com.chromascape.utils.domain.ocr.Ocr; import java.awt.Point; -import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.IOException; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.Random; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -67,7 +65,7 @@ public class DemoAgilityScript extends BaseScript { private static final Point RESET_TILE = ROOFTOP_RESET_TILES.get("Canifis"); private static final int TIMEOUT_XP_CHANGE = 15; - private static final int TIMEOUT_GREEN_APPEAR = 10; + private static final int TIMEOUT_OBSTACLE_APPEAR = 10; // Colour Definitions // These are instantiated as final fields to prevent unnecessary memory allocation during cycles @@ -76,17 +74,9 @@ public class DemoAgilityScript extends BaseScript { private static final ColourObj MARK_COLOUR = new ColourObj("red", new Scalar(0, 254, 254, 0), new Scalar(1, 255, 255, 0)); - // Cached OCR colour object to reduce lookup overhead in repetitive loops - private static ColourObj TEXT_COLOUR_WHITE = null; - // Random used in randomising break times between obstacles private final Random random = new Random(); - /** Initializes the script and pre-loads necessary colour instances. */ - public DemoAgilityScript() { - TEXT_COLOUR_WHITE = ColourInstances.getByName("White"); - } - /** * The main execution loop of the script. * @@ -101,13 +91,29 @@ public DemoAgilityScript() { */ @Override protected void cycle() { - String previousXp = getXp(); + // Log the current XP before clicking obstacle for comparison later + // The idea is to click the obstacle then wait for XP change then loop + int previousXp = -1; + try { + // Read XP + previousXp = Minimap.getXp(this); + // Make sure it's read properly + if (previousXp == -1) { + stop(); + DiscordNotification.send("Xp could not be read."); + } + } catch (IOException e) { + logger.error(e); + stop(); + DiscordNotification.send( + "Bot couldn't read XP bar because of OCR font library load, stopping and logging :("); + } // Check the state of the course if (!isObstacleVisible()) { if (handleMarkOrLost()) { // If we clicked a mark, wait for the course to reset and the Green highlight to appear - waitForObstacleToAppear(TIMEOUT_GREEN_APPEAR); + waitForObstacleToAppear(); return; } } @@ -119,10 +125,12 @@ protected void cycle() { } catch (Exception e) { logger.error("Mouse movement interrupted while clicking moving object: {}", e.getMessage()); stop(); + DiscordNotification.send( + "Mouse movement interrupted while clicking moving object: " + e.getMessage()); } // Wait for the action to complete via XP update - waitUntilXpChange(previousXp, TIMEOUT_XP_CHANGE); + waitUntilXpChange(previousXp); // Humanizing sleep to mimic natural player behavior // And to prevent overloading moving object logic @@ -151,7 +159,7 @@ private boolean handleMarkOrLost() { waitRandomMillis(600, 800); if (!isObstacleVisible()) { try { - logger.info("Lost detected. Walking to reset tile."); + logger.info("We are lost. Walking to reset tile."); controller().walker().pathTo(RESET_TILE, true); waitRandomMillis(4000, 6000); } catch (Exception e) { @@ -162,21 +170,6 @@ private boolean handleMarkOrLost() { return false; } - /** - * Extracts the current Total XP from beside the minimap UI element using OCR. - * - * @return the XP string - */ - private String getXp() { - Rectangle xpZone = controller().zones().getMinimap().get("totalXP"); - try { - return Ocr.extractText(xpZone, "Plain 12", TEXT_COLOUR_WHITE, true); - } catch (IOException e) { - logger.error("Images could not be read from disk {}", e.getMessage()); - } - return ""; - } - /** * Scans the game view for the Red colour associated with a Mark of Grace and attempts to click * it. @@ -207,25 +200,24 @@ private boolean clickMarkOfGraceIfPresent() { * Blocks execution until the Total XP value changes or the timeout is reached. * * @param previousXp the XP value captured before the action started - * @param timeoutSeconds the maximum duration to wait in seconds */ - private void waitUntilXpChange(String previousXp, int timeoutSeconds) { - LocalDateTime endTime = LocalDateTime.now().plusSeconds(timeoutSeconds); + private void waitUntilXpChange(int previousXp) { + LocalDateTime endTime = LocalDateTime.now().plusSeconds(TIMEOUT_XP_CHANGE); // Ensure we do not hang if the initial OCR read failed and returned an empty string - while (!previousXp.isEmpty() - && Objects.equals(previousXp, getXp()) - && LocalDateTime.now().isBefore(endTime)) { - waitMillis(300); + try { + while (previousXp == Minimap.getXp(this) && LocalDateTime.now().isBefore(endTime)) { + waitMillis(300); + } + } catch (Exception e) { + logger.error(e); + stop(); + DiscordNotification.send("Bot couldn't read XP bar, stopping"); } } - /** - * Blocks execution until the obstacle highlight appears or the timeout is reached. - * - * @param timeoutSeconds the maximum duration to wait in seconds - */ - private void waitForObstacleToAppear(int timeoutSeconds) { - LocalDateTime endTime = LocalDateTime.now().plusSeconds(timeoutSeconds); + /** Blocks execution until the obstacle highlight appears or the timeout is reached. */ + private void waitForObstacleToAppear() { + LocalDateTime endTime = LocalDateTime.now().plusSeconds(TIMEOUT_OBSTACLE_APPEAR); while (!isObstacleVisible() && LocalDateTime.now().isBefore(endTime)) { waitMillis(300); } diff --git a/src/main/java/com/chromascape/scripts/DemoFishingScript.java b/src/main/java/com/chromascape/scripts/DemoFishingScript.java index e99bf46..dd93d6a 100644 --- a/src/main/java/com/chromascape/scripts/DemoFishingScript.java +++ b/src/main/java/com/chromascape/scripts/DemoFishingScript.java @@ -44,11 +44,6 @@ public class DemoFishingScript extends BaseScript { private static String lastMessage; - /** Constructs a BaseScript. */ - public DemoFishingScript() { - super(); - } - /** * Overridden cycle. Repeats all tasks within, until stop() is called from either the Web UI, or * from within the script. diff --git a/src/main/java/com/chromascape/scripts/DemoMiningScript.java b/src/main/java/com/chromascape/scripts/DemoMiningScript.java index 3c8d5b7..69c3fa8 100644 --- a/src/main/java/com/chromascape/scripts/DemoMiningScript.java +++ b/src/main/java/com/chromascape/scripts/DemoMiningScript.java @@ -31,11 +31,6 @@ public class DemoMiningScript extends BaseScript { private static final Logger logger = LogManager.getLogger(DemoMiningScript.class); private static final String ironOre = "/images/user/Iron_ore.png"; - /** Constructs a new mining script. */ - public DemoMiningScript() { - super(); - } - /** * Executes one cycle of the script logic. * diff --git a/src/main/java/com/chromascape/scripts/DemoWineScript.java b/src/main/java/com/chromascape/scripts/DemoWineScript.java index 554bb21..ede364b 100644 --- a/src/main/java/com/chromascape/scripts/DemoWineScript.java +++ b/src/main/java/com/chromascape/scripts/DemoWineScript.java @@ -36,18 +36,10 @@ public class DemoWineScript extends BaseScript { private boolean bankFlag = true; - /** Constructs a BaseScript. */ - public DemoWineScript() { - super(); - } - /** - * The core logic of the script. - * - *
This method is called repeatedly in a loop by {@link #run()} for the specified duration. - * Subclasses must override this method to implement their specific bot behavior. - * - *
Note: This method is called synchronously on the running thread. + * The core logic of the script. This function will loop repeatedly until {@link #stop()} is + * called. You should avoid putting all your script logic directly inside this function, instead + * split it up into other functions as shown below. */ @Override protected void cycle() { diff --git a/src/main/java/com/chromascape/scripts/Screenshotter.java b/src/main/java/com/chromascape/scripts/Screenshotter.java index 6f48a40..3617923 100644 --- a/src/main/java/com/chromascape/scripts/Screenshotter.java +++ b/src/main/java/com/chromascape/scripts/Screenshotter.java @@ -9,10 +9,11 @@ import org.apache.logging.log4j.Logger; /** - * Creates screenshots of the client and stores it in an external folder "screenshots". Screenshots - * are saved as "original.png". Screenshots taken by this class are used by the colour picker in the - * web UI. This is a single cycle program that exits out immediately after completion. Served as a - * "user script" in the web UI. + * Creates screenshots of the client and stores it in an external folder "/output". Screenshots are + * saved as "original.png". Screenshots taken by this class are used by the colour picker in the web + * UI. This is a single cycle program that exits out immediately after completion. Served as a "user + * script" in the web UI, however started via a dedicated button, rather than started as a normal + * script. */ public class Screenshotter extends BaseScript { @@ -30,9 +31,9 @@ public Screenshotter() { } /** - * Takes a screenshot and saves it in the "screenshots" folder outside the project directory. - * Although in the BaseScript - this function is repeated until the specified time duration is met - * - Here because this is a one time task it exits out early by calling "stop()". + * Takes a screenshot and saves it in the "/output" directory on the same level as /src. Although + * in the BaseScript - this function is repeated until the specified time duration is met - Here, + * because this is a one time task it exits out early by calling "stop()". */ @Override protected void cycle() { diff --git a/src/main/java/com/chromascape/utils/actions/PointSelector.java b/src/main/java/com/chromascape/utils/actions/PointSelector.java index a14c7dc..0544459 100644 --- a/src/main/java/com/chromascape/utils/actions/PointSelector.java +++ b/src/main/java/com/chromascape/utils/actions/PointSelector.java @@ -227,7 +227,8 @@ private static Point findPointInColourInternal( return null; } - ChromaObj obj = objs.get(0); + // Use the closest object to screen centre since only one object is desired + ChromaObj obj = ColourContours.getChromaObjClosestToCentre(objs); try { int attempts = 0; // Generate initial point using the function provided (Heuristic or Tightness) diff --git a/src/main/java/com/chromascape/utils/core/AccountManager.java b/src/main/java/com/chromascape/utils/core/AccountManager.java deleted file mode 100644 index 32d5554..0000000 --- a/src/main/java/com/chromascape/utils/core/AccountManager.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.chromascape.utils.core; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Singleton class to manage the selected account across the entire application. This provides a - * centralized way to store and access the currently selected account. - */ -public class AccountManager { - - private static final Logger logger = LoggerFactory.getLogger(AccountManager.class); - - // Static variable that persists across all classes - private static String selectedAccount = null; - - // Private constructor to prevent instantiation - private AccountManager() { - throw new UnsupportedOperationException( - "AccountManager is a utility class and cannot be instantiated"); - } - - /** - * Gets the currently selected account. - * - * @return The selected account name, or null if none is selected - */ - public static String getSelectedAccount() { - return selectedAccount; - } - - /** - * Sets the selected account. - * - * @param account The account name to set - */ - public static void setSelectedAccount(String account) { - selectedAccount = account; - logger.info("Selected account updated to: {}", account); - } - - /** Clears the selected account. */ - public static void clearSelectedAccount() { - selectedAccount = null; - logger.info("Selected account cleared"); - } - - /** - * Checks if an account is currently selected. - * - * @return true if an account is selected, false otherwise - */ - public static boolean hasSelectedAccount() { - return selectedAccount != null && !selectedAccount.trim().isEmpty(); - } - - /** - * Gets the selected account or returns a default value if none is selected. - * - * @param defaultValue The default value to return if no account is selected - * @return The selected account or the default value - */ - public static String getSelectedAccountOrDefault(String defaultValue) { - return hasSelectedAccount() ? selectedAccount : defaultValue; - } -} diff --git a/src/main/java/com/chromascape/utils/core/constants/CacheFolderConstants.java b/src/main/java/com/chromascape/utils/core/constants/CacheFolderConstants.java deleted file mode 100644 index 4b53834..0000000 --- a/src/main/java/com/chromascape/utils/core/constants/CacheFolderConstants.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.chromascape.utils.core.constants; - -/** - * The {@code CacheFolderConstants} class defines directory and file naming conventions for the - * ChromaScape cache structure. - * - *
Purpose: Centralizes folder and file names used for local cache organization, - * configuration, logs, scripts, and data files, ensuring consistency across the ChromaScape - * framework. - * - *
Features: - * - *
These constants are intended for use wherever cache-related paths are constructed or - * referenced within ChromaScape. - * - *
Example usage: - * - *
- * String configPath = - * Path.of( - * CacheFolderConstants.CHROMA_CACHE_FOLDER_NAME, - * CacheFolderConstants.CONFIG_FOLDER_NAME) - * .toString(); - *- */ -public class CacheFolderConstants { - public static final String CHROMA_CACHE_FOLDER_NAME = ".chromascape"; - public static final String CONFIG_FOLDER_NAME = "config"; - public static final String LOGS_FOLDER_NAME = "logs"; - public static final String SCRIPTS_FOLDER_NAME = "scripts"; - public static final String DATA_FOLDER_NAME = "data"; - public static final String CACHE_FOLDER_NAME = "cache"; - public static final String[] CHROMA_CACHE_FOLDER_SUBDIRS = { - CONFIG_FOLDER_NAME, LOGS_FOLDER_NAME, SCRIPTS_FOLDER_NAME, DATA_FOLDER_NAME, CACHE_FOLDER_NAME - }; - public static final String ACCOUNTS_FILE_NAME = "accounts.json"; -} diff --git a/src/main/java/com/chromascape/utils/core/input/keyboard/VirtualKeyboardUtils.java b/src/main/java/com/chromascape/utils/core/input/keyboard/VirtualKeyboardUtils.java index fd80672..ce07630 100644 --- a/src/main/java/com/chromascape/utils/core/input/keyboard/VirtualKeyboardUtils.java +++ b/src/main/java/com/chromascape/utils/core/input/keyboard/VirtualKeyboardUtils.java @@ -34,7 +34,17 @@ public synchronized void sendKeyChar(char keyChar) { BaseScript.checkInterrupted(); StateManager.setState(BotState.ACTING); StatisticsManager.incrementInputs(); - kinput.sendKeyEvent(400, keyChar); + kinput.sendCharEvent(keyChar); + } + + /** + * Updates the necessary states of the bot for the {@link BaseScript}'s stop() function and + * updates the BotState for the UI. + */ + private void prepareInput() { + BaseScript.checkInterrupted(); + StateManager.setState(BotState.ACTING); + StatisticsManager.incrementInputs(); } /** @@ -46,9 +56,7 @@ public synchronized void sendKeyChar(char keyChar) { * @throws IllegalArgumentException if the key name is invalid. */ public synchronized void sendModifierKey(int eventId, String key) { - BaseScript.checkInterrupted(); - StateManager.setState(BotState.ACTING); - StatisticsManager.incrementInputs(); + prepareInput(); int keyId = switch (key.toLowerCase()) { case "shift" -> 16; @@ -59,7 +67,19 @@ public synchronized void sendModifierKey(int eventId, String key) { case "space" -> 32; default -> throw new IllegalArgumentException("Invalid modifier key: " + key); }; - kinput.sendModifierKey(eventId, key, keyId); + kinput.sendVirtualKeyEvent(eventId, keyId, key); + } + + /** + * Sends a function key (F1-F12) using the KeyCode slot. + * + * @param eventId 401 to simulate a key press, or 402 to simulate a key release. + * @param functionKeyNumber The number of the F key (e.g., 6 for F6 or 1 for F1). + */ + public synchronized void sendFunctionKey(int eventId, int functionKeyNumber) { + prepareInput(); + int keyId = 111 + functionKeyNumber; // F1 starts at 112 + kinput.sendVirtualKeyEvent(eventId, keyId, "F" + functionKeyNumber); } /** @@ -70,17 +90,15 @@ public synchronized void sendModifierKey(int eventId, String key) { * @throws IllegalArgumentException if the arrow direction is invalid. */ public synchronized void sendArrowKey(int eventId, String key) { - BaseScript.checkInterrupted(); - StateManager.setState(BotState.ACTING); - StatisticsManager.incrementInputs(); + prepareInput(); int keyId = switch (key.toLowerCase()) { case "left" -> 37; - case "right" -> 39; case "up" -> 38; + case "right" -> 39; case "down" -> 40; default -> throw new IllegalArgumentException("Invalid arrow key: " + key); }; - kinput.sendArrowKey(eventId, key, keyId); + kinput.sendVirtualKeyEvent(eventId, keyId, key); } } diff --git a/src/main/java/com/chromascape/utils/core/input/mouse/VirtualMouseUtils.java b/src/main/java/com/chromascape/utils/core/input/mouse/VirtualMouseUtils.java index 72034eb..b6afe5d 100644 --- a/src/main/java/com/chromascape/utils/core/input/mouse/VirtualMouseUtils.java +++ b/src/main/java/com/chromascape/utils/core/input/mouse/VirtualMouseUtils.java @@ -200,7 +200,7 @@ public void leftClick() { performClick( (p) -> { synchronized (kinputLock) { - kinput.clickLeft(p.x, p.y); + kinput.clickMouse(p.x, p.y, Kinput.MouseButton.LEFT.id); // Redundant move ensures OS registers click at correct coords if jitter occurred kinput.moveMouse(p.x, p.y); } @@ -212,7 +212,7 @@ public void rightClick() { performClick( (p) -> { synchronized (kinputLock) { - kinput.clickRight(p.x, p.y); + kinput.clickMouse(p.x, p.y, Kinput.MouseButton.RIGHT.id); kinput.moveMouse(p.x, p.y); } }); diff --git a/src/main/java/com/chromascape/utils/core/input/remoteinput/Kinput.java b/src/main/java/com/chromascape/utils/core/input/remoteinput/Kinput.java index fceeda1..b0dfe8f 100644 --- a/src/main/java/com/chromascape/utils/core/input/remoteinput/Kinput.java +++ b/src/main/java/com/chromascape/utils/core/input/remoteinput/Kinput.java @@ -175,11 +175,6 @@ private static KinputInterface loadLibrary() { } } - /** Returns the current system time in milliseconds. */ - private long now() { - return System.currentTimeMillis(); - } - /** Sleeps briefly to simulate human-like click timing. */ private void sleepHumanClick() { try { @@ -196,38 +191,48 @@ private synchronized void focus() { } } - /** Sends a left mouse click at the specified screen coordinate. */ - public synchronized void clickLeft(int x, int y) { - focus(); - if (!kinput.KInput_MouseEvent( - pid, MouseEventType.MOUSE_PRESS.id, now(), 1, x, y, 1, false, MouseButton.LEFT.id)) { - throw new RuntimeException("Left mouse press failed"); - } - sleepHumanClick(); - if (!kinput.KInput_MouseEvent( - pid, MouseEventType.MOUSE_RELEASE.id, now(), 1, x, y, 1, false, MouseButton.LEFT.id)) { - throw new RuntimeException("Left mouse release failed"); - } - } - - /** Sends a right mouse click at the specified screen coordinate. */ - public synchronized void clickRight(int x, int y) { + /** + * Sends a mouse press and release to a specified screen co-ordinate. This operates in client + * relative co-ordinates, not screen relative co-ordinates. + * + * @param x the x co-ordinate of the action. + * @param y the y co-ordinate of the action. + * @param button which {@link MouseButton} to use. + */ + public synchronized void clickMouse(int x, int y, int button) { focus(); if (!kinput.KInput_MouseEvent( - pid, MouseEventType.MOUSE_PRESS.id, now(), 0, x, y, 1, false, MouseButton.RIGHT.id)) { - throw new RuntimeException("Right mouse press failed"); + pid, + MouseEventType.MOUSE_PRESS.id, + System.currentTimeMillis(), + 1, + x, + y, + 1, + false, + button)) { + throw new RuntimeException(button + " press failed"); } sleepHumanClick(); if (!kinput.KInput_MouseEvent( - pid, MouseEventType.MOUSE_RELEASE.id, now(), 0, x, y, 1, false, MouseButton.RIGHT.id)) { - throw new RuntimeException("Right mouse release failed"); + pid, + MouseEventType.MOUSE_RELEASE.id, + System.currentTimeMillis(), + 1, + x, + y, + 1, + false, + button)) { + throw new RuntimeException(button + " release failed"); } } /** Sends a middle mouse button input. */ public synchronized void middleInput(int x, int y, int eventID) { focus(); - if (!kinput.KInput_MouseEvent(pid, eventID, now(), 1, x, y, 1, false, MouseButton.MIDDLE.id)) { + if (!kinput.KInput_MouseEvent( + pid, eventID, System.currentTimeMillis(), 1, x, y, 1, false, MouseButton.MIDDLE.id)) { throw new RuntimeException("Middle mouse event failed"); } } @@ -236,11 +241,27 @@ public synchronized void middleInput(int x, int y, int eventID) { public synchronized void moveMouse(int x, int y) { focus(); if (!kinput.KInput_MouseEvent( - pid, MouseEventType.MOUSE_ENTER.id, now(), 0, x, y, 0, false, MouseButton.NONE.id)) { + pid, + MouseEventType.MOUSE_ENTER.id, + System.currentTimeMillis(), + 0, + x, + y, + 0, + false, + MouseButton.NONE.id)) { throw new RuntimeException("Mouse enter failed"); } if (!kinput.KInput_MouseEvent( - pid, MouseEventType.MOUSE_MOVE.id, now(), 0, x, y, 0, false, MouseButton.NONE.id)) { + pid, + MouseEventType.MOUSE_MOVE.id, + System.currentTimeMillis(), + 0, + x, + y, + 0, + false, + MouseButton.NONE.id)) { throw new RuntimeException("Mouse move failed"); } } @@ -253,34 +274,48 @@ pid, MouseEventType.MOUSE_MOVE.id, now(), 0, x, y, 0, false, MouseButton.NONE.id */ public synchronized void sendKeyEvent(int eventID, char keyChar) { focus(); - if (!kinput.KInput_KeyEvent(pid, eventID, now(), 0, 0, (short) keyChar, 0)) { + if (!kinput.KInput_KeyEvent( + pid, eventID, System.currentTimeMillis(), 0, 0, (short) keyChar, 0)) { throw new RuntimeException("Key event failed for char: " + keyChar); } } - /** Sends a key event for a modifier (e.g. Shift, Ctrl, Alt). */ - public synchronized void sendModifierKey(int eventID, String key, int keyID) { + /** + * Types a character to the client window. + * + * @param keyChar The character to send. + */ + public synchronized void sendCharEvent(char keyChar) { focus(); - if (!kinput.KInput_KeyEvent(pid, eventID, now(), 0, keyID, (short) 0, 0)) { - throw new RuntimeException("Modifier key event failed for key: " + key); + // Passes keyChar into the 'short' parameter (arg5) + if (!kinput.KInput_KeyEvent(pid, 400, System.currentTimeMillis(), 0, 0, (short) keyChar, 0)) { + throw new RuntimeException("Character event failed for char: '" + keyChar + "'"); } } - /** Sends a directional arrow key event. */ - public synchronized void sendArrowKey(int eventID, String key, int keyID) { + /** + * Sends a virtual key event (Arrow, Modifier, F-Key) to the client window's SunAwtCanvas. + * + * @param eventID 401 for press, 402 for release. + * @param keyCode The virtual key code. + * @param keyName A descriptive name used purely for error logging. + */ + public synchronized void sendVirtualKeyEvent(int eventID, int keyCode, String keyName) { focus(); - if (!kinput.KInput_KeyEvent(pid, eventID, now(), 0, keyID, (short) 0, 0)) { - throw new RuntimeException("Arrow key event failed for key: " + key); + // Passes keyCode into the integer parameter (arg4) + if (!kinput.KInput_KeyEvent( + pid, eventID, System.currentTimeMillis(), 0, keyCode, (short) 0, 0)) { + throw new RuntimeException("Virtual key event failed for: " + keyName); } } /** - * Destroys the active Kinput instance. Should be called on shutdown to free memory and close + * Destroys the active KInput instance. Should be called on shutdown to free memory and close * resources. */ public synchronized void destroy() { if (!kinput.KInput_Delete(pid)) { - throw new RuntimeException("Failed to delete Kinput instance"); + throw new RuntimeException("Failed to delete KInput instance"); } } } diff --git a/src/main/java/com/chromascape/utils/core/screen/topology/ColourContours.java b/src/main/java/com/chromascape/utils/core/screen/topology/ColourContours.java index 9eb373f..1f2281d 100644 --- a/src/main/java/com/chromascape/utils/core/screen/topology/ColourContours.java +++ b/src/main/java/com/chromascape/utils/core/screen/topology/ColourContours.java @@ -24,8 +24,8 @@ */ public class ColourContours { - private static final Mat DILATE_KERNEL = getStructuringElement(MORPH_ELLIPSE, new Size(25, 25)); - private static final Mat ERODE_KERNEL = getStructuringElement(MORPH_ELLIPSE, new Size(25, 25)); + private static final Mat DILATE_KERNEL = getStructuringElement(MORPH_ELLIPSE, new Size(20, 20)); + private static final Mat ERODE_KERNEL = getStructuringElement(MORPH_ELLIPSE, new Size(20, 20)); private static final Scalar COLOUR_WHITE = new Scalar(255); private static final Mat EMPTY_HIERARCHY = new Mat(); private static final org.bytedeco.opencv.opencv_core.Point OFFSET_ZERO = @@ -42,11 +42,50 @@ public class ColourContours { public static List
This class provides a single static method, {@link #match}, which uses the TM_SQDIFF_NORMED * algorithm to locate a template image within a larger base image. It uses an alpha mask to ignore - * transparent pixels in the template, allowing for accurate matching even with irregular shapes. + * transparent pixels in the template. * - *
This is commonly used in ChromaScape to locate UI elements or patterns in the client window, - * based on screen captures and template assets. - * - *
All memory allocated in native OpenCV (Mats and pointers) is explicitly released before - * return. + *
This is commonly to locate UI elements or sprites in the client window, based on screen + * captures and template assets. */ public class TemplateMatching { @@ -56,19 +53,16 @@ public class TemplateMatching { *
The method returns the bounding rectangle of the best match if its matching score is below * the given threshold. If no match satisfies the threshold, the method returns {@code null}. * - *
Note: The method releases the native OpenCV memory of the input and intermediate matrices,
- * so the caller should not use the input Mats after this call.
- *
- * @param templateImg The template image (smaller), expected as a BufferedImage in BGRA format or
- * convertible to it.
- * @param baseImg The base image (larger) where the template is searched, expected as a
- * BufferedImage in BGRA format or convertible to it.
+ * @param templateImg The template image (smaller), expected as a {@link BufferedImage} in BGRA
+ * format or convertible to it.
+ * @param baseImg The base image (larger) where the template is searched, expected as a {@link
+ * BufferedImage} in BGRA format or convertible to it.
* @param threshold The maximum allowed normalized squared difference score for a valid match.
* Lower values mean better matches.
* @param debugMsg Set this to true if you want detailed messages useful when debugging.
* @return A {@link Rectangle} representing the position and size of the matching area in the base
* image, or {@code null} if no match meets the threshold criteria.
- * @throws IllegalArgumentException If either input Mat is empty (not loaded).
+ * @throws IllegalArgumentException If either input image is empty (not loaded).
* @throws Exception If the template is larger than the base image.
*/
public static Rectangle match(
diff --git a/src/main/java/com/chromascape/utils/core/screen/window/WindowHandler.java b/src/main/java/com/chromascape/utils/core/screen/window/WindowHandler.java
index 60f2465..8c4f3f8 100644
--- a/src/main/java/com/chromascape/utils/core/screen/window/WindowHandler.java
+++ b/src/main/java/com/chromascape/utils/core/screen/window/WindowHandler.java
@@ -1,6 +1,5 @@
package com.chromascape.utils.core.screen.window;
-import com.chromascape.utils.core.AccountManager;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinDef.HWND;
@@ -17,7 +16,7 @@
*/
public class WindowHandler {
private static final Logger logger = LoggerFactory.getLogger(WindowHandler.class);
- private static final String _windowName = "RuneLite";
+ private static final String WINDOW_NAME = "RuneLite";
/**
* JNA interface for accessing low-level Win32 User32 functions that are not provided by the
@@ -79,7 +78,7 @@ public interface User32 extends StdCallLibrary {
}
/**
- * Attempts to locate the window whose title matches the {@code windowName}.
+ * Attempts to locate the window whose title matches the {@code WINDOW_NAME}.
*
* @return The {@link HWND} handle of the target window, or {@code null} if not found.
*/
@@ -87,22 +86,13 @@ public static HWND getTargetWindow() {
AtomicReference This implementation solves the "backend overload" problem by ensuring that only one request
- * is ever in flight per slider. If the user moves the slider repeatedly while a request is processing,
- * intermediate values are locally overwritten (conflated). As soon as the active request finishes,
- * the loop immediately picks up the current latest value and sends it.
- *
- * This guarantees the server always receives the final state eventually without lag accumulation.
+ * To ensure that the slider state doesn't overload the backend.
*
* @param {string} sliderName - The ID/name of the slider being updated.
* @param {string|number} value - The new value of the slider.
@@ -283,4 +278,16 @@ function toCamelCase(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
}).replace(/\s+/g, '');
-}
\ No newline at end of file
+}
+/**
+ * Initializes the colour name input listener to update the code snippet in real-time.
+ */
+function initNameInput() {
+ const nameInput = document.getElementById("colourNameInput");
+ if (nameInput) {
+ // 'input' fires on every keystroke, 'change' only when focus is lost
+ nameInput.addEventListener("input", () => {
+ updateCodeSnippet();
+ });
+ }
+}
diff --git a/src/test/java/com/chromascape/web/config/StartupConfigurationTest.java b/src/test/java/com/chromascape/web/config/StartupConfigurationTest.java
deleted file mode 100644
index 9db92ea..0000000
--- a/src/test/java/com/chromascape/web/config/StartupConfigurationTest.java
+++ /dev/null
@@ -1,170 +0,0 @@
-package com.chromascape.web.config;
-
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import java.io.File;
-import java.lang.reflect.Method;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
-import org.slf4j.Logger;
-import org.springframework.test.util.ReflectionTestUtils;
-
-/** Test class for {@link StartupConfiguration}. */
-class StartupConfigurationTest {
-
- @TempDir File tempDir;
-
- private StartupConfiguration startupConfiguration;
- private static Logger mockLogger;
-
- @BeforeEach
- void setUp() {
- startupConfiguration = new StartupConfiguration();
- mockLogger = mock(Logger.class); // This will now create an SLF4J mock
- ReflectionTestUtils.setField(startupConfiguration, "logger", mockLogger);
- }
-
- @Test
- void testStartupConfigurationCanBeInstantiated() {
- assertNotNull(startupConfiguration, "StartupConfiguration should be instantiable");
- }
-
- @Test
- void testInitializeInfrastructureCreatesChromascapeDirectory() throws Exception {
- final String originalUserDir = System.getProperty("user.dir");
- System.setProperty("user.dir", tempDir.getAbsolutePath());
-
- startupConfiguration.initializeInfrastructure();
-
- File expectedDir = new File(tempDir, ".chromascape");
- assertTrue(expectedDir.exists(), ".chromascape directory should be created");
- assertTrue(expectedDir.isDirectory(), ".chromascape should be a directory");
- assertTrue(expectedDir.canWrite(), ".chromascape directory should be writable");
-
- verify(mockLogger, atLeastOnce()).info(anyString());
-
- System.setProperty("user.dir", originalUserDir);
- }
-
- @Test
- void testInitializeInfrastructureCreatesSubdirectories() throws Exception {
- final String originalUserDir = System.getProperty("user.dir");
- System.setProperty("user.dir", tempDir.getAbsolutePath());
-
- startupConfiguration.initializeInfrastructure();
-
- File chromascapeDir = new File(tempDir, ".chromascape");
- String[] expectedSubdirs = {"config", "logs", "scripts", "data", "cache"};
- for (String subdir : expectedSubdirs) {
- File subdirFile = new File(chromascapeDir, subdir);
- assertTrue(subdirFile.exists(), "Subdirectory " + subdir + " should exist");
- assertTrue(subdirFile.isDirectory(), "Subdirectory " + subdir + " should be a directory");
- }
-
- System.setProperty("user.dir", originalUserDir);
- }
-
- @Test
- void testInitializeInfrastructureHandlesExistingDirectory() throws Exception {
- final String originalUserDir = System.getProperty("user.dir");
- System.setProperty("user.dir", tempDir.getAbsolutePath());
-
- File existingDir = new File(tempDir, ".chromascape");
- existingDir.mkdirs();
-
- startupConfiguration.initializeInfrastructure();
-
- assertTrue(existingDir.exists(), "Existing directory should still exist");
- verify(mockLogger, atLeastOnce()).info(anyString());
-
- System.setProperty("user.dir", originalUserDir);
- }
-
- @Test
- void testInitializeInfrastructureLogsAppropriateMessages() throws Exception {
- final String originalUserDir = System.getProperty("user.dir");
- System.setProperty("user.dir", tempDir.getAbsolutePath());
-
- startupConfiguration.initializeInfrastructure();
-
- verify(mockLogger, atLeastOnce()).info(anyString());
-
- System.setProperty("user.dir", originalUserDir);
- }
-
- @Test
- void testInitializeInfrastructureHandlesDirectoryCreationFailure() throws Exception {
- final String originalUserDir = System.getProperty("user.dir");
- System.setProperty("user.dir", "/root/restricted");
-
- try {
- startupConfiguration.initializeInfrastructure();
- } catch (RuntimeException e) {
- assertTrue(e.getMessage().contains("Failed to initialize .chromascape directory"));
- }
-
- System.setProperty("user.dir", originalUserDir);
- }
-
- @Test
- void testCreateSubdirectoriesCreatesAllRequiredSubdirectories() throws Exception {
- Method createSubdirectoriesMethod =
- StartupConfiguration.class.getDeclaredMethod("createSubdirectories", File.class);
- createSubdirectoriesMethod.setAccessible(true);
-
- File testDir = new File(tempDir, "test-chromascape");
- testDir.mkdirs();
-
- createSubdirectoriesMethod.invoke(startupConfiguration, testDir);
-
- String[] expectedSubdirs = {"config", "logs", "scripts", "data", "cache"};
- for (String subdir : expectedSubdirs) {
- File subdirFile = new File(testDir, subdir);
- assertTrue(subdirFile.exists(), "Subdirectory " + subdir + " should be created");
- }
- }
-
- @Test
- void testCreateSubdirectoriesHandlesExistingSubdirectories() throws Exception {
- Method createSubdirectoriesMethod =
- StartupConfiguration.class.getDeclaredMethod("createSubdirectories", File.class);
- createSubdirectoriesMethod.setAccessible(true);
-
- File testDir = new File(tempDir, "test-chromascape");
- testDir.mkdirs();
-
- File existingSubdir = new File(testDir, "config");
- existingSubdir.mkdirs();
-
- createSubdirectoriesMethod.invoke(startupConfiguration, testDir);
-
- String[] expectedSubdirs = {"config", "logs", "scripts", "data", "cache"};
- for (String subdir : expectedSubdirs) {
- File subdirFile = new File(testDir, subdir);
- assertTrue(subdirFile.exists(), "Subdirectory " + subdir + " should exist");
- }
- }
-
- @Test
- void testInitializeChromascapeDirectoryPrivateMethod() throws Exception {
- Method initializeChromascapeDirectoryMethod =
- StartupConfiguration.class.getDeclaredMethod("initializeChromascapeDirectory");
- initializeChromascapeDirectoryMethod.setAccessible(true);
-
- final String originalUserDir = System.getProperty("user.dir");
- System.setProperty("user.dir", tempDir.getAbsolutePath());
-
- initializeChromascapeDirectoryMethod.invoke(startupConfiguration);
-
- File expectedDir = new File(tempDir, ".chromascape");
- assertTrue(expectedDir.exists(), ".chromascape directory should be created");
-
- System.setProperty("user.dir", originalUserDir);
- }
-}
diff --git a/third_party/KInput/KInput/KInput/bin/Release/KInput.dll b/third_party/KInput/KInput/KInput/bin/Release/KInput.dll
index 71b2c8d..9baca1d 100644
Binary files a/third_party/KInput/KInput/KInput/bin/Release/KInput.dll and b/third_party/KInput/KInput/KInput/bin/Release/KInput.dll differ
diff --git a/third_party/KInput/KInput/KInput/makefile b/third_party/KInput/KInput/KInput/makefile
index edb2054..5465995 100644
--- a/third_party/KInput/KInput/KInput/makefile
+++ b/third_party/KInput/KInput/KInput/makefile
@@ -11,14 +11,14 @@ AR = ar.exe
LD = g++.exe
WINDRES = windres.exe
-INC = -I"..\..\..\minhook\include"
-CFLAGS =
-RESINC =
-LIBDIR = -L"..\..\..\minhook\build\MinGW"
-LIB = -l"MinHook"
+INC =
+CFLAGS =
+RESINC =
+LIBDIR =
+LIB =
LDFLAGS =
-INC_RELEASE = $(INC) -I"C:\Program Files\Microsoft\jdk-17.0.16.8-hotspot\include" -I"C:\Program Files\Microsoft\jdk-17.0.16.8-hotspot\include\win32"
+INC_RELEASE = -I"C:\Program Files\Microsoft\jdk-17.0.16.8-hotspot\include" -I"C:\Program Files\Microsoft\jdk-17.0.16.8-hotspot\include\win32"
CFLAGS_RELEASE = $(CFLAGS) -Os -Wall -std=c++1z -DBUILD_DLL
RESINC_RELEASE = $(RESINC)
RCFLAGS_RELEASE = $(RCFLAGS)
@@ -35,16 +35,16 @@ all: release
clean: clean_release
-before_release:
+before_release:
cmd /c if not exist bin\\Release md bin\\Release
cmd /c if not exist $(OBJDIR_RELEASE) md $(OBJDIR_RELEASE)
-after_release:
+after_release:
release: before_release out_release after_release
out_release: before_release $(OBJ_RELEASE) $(DEP_RELEASE)
- $(LD) -shared $(LIBDIR_RELEASE) $(OBJ_RELEASE) -o $(OUT_RELEASE) $(LDFLAGS_RELEASE) $(LIB_RELEASE)
+ $(LD) -shared $(OBJ_RELEASE) -o $(OUT_RELEASE) $(LDFLAGS_RELEASE)
$(OBJDIR_RELEASE)\\KInput.o: KInput.cpp
$(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c KInput.cpp -o $(OBJDIR_RELEASE)\\KInput.o
@@ -52,7 +52,7 @@ $(OBJDIR_RELEASE)\\KInput.o: KInput.cpp
$(OBJDIR_RELEASE)\\main.o: main.cpp
$(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c main.cpp -o $(OBJDIR_RELEASE)\\main.o
-clean_release:
+clean_release:
cmd /c del /f $(OBJ_RELEASE) $(OUT_RELEASE)
cmd /c rd bin\\Release
cmd /c rd $(OBJDIR_RELEASE)
diff --git a/third_party/KInput/KInput/KInput/obj/Release/KInput.o b/third_party/KInput/KInput/KInput/obj/Release/KInput.o
index 0c0d86a..780db6c 100644
Binary files a/third_party/KInput/KInput/KInput/obj/Release/KInput.o and b/third_party/KInput/KInput/KInput/obj/Release/KInput.o differ
diff --git a/third_party/KInput/KInput/KInput/obj/Release/main.o b/third_party/KInput/KInput/KInput/obj/Release/main.o
index 6b36669..7bf5a9f 100644
Binary files a/third_party/KInput/KInput/KInput/obj/Release/main.o and b/third_party/KInput/KInput/KInput/obj/Release/main.o differ
diff --git a/third_party/KInput/KInput/KInputCtrl/bin/Release/KInputCtrl.dll b/third_party/KInput/KInput/KInputCtrl/bin/Release/KInputCtrl.dll
index 9fee134..eddf38e 100644
Binary files a/third_party/KInput/KInput/KInputCtrl/bin/Release/KInputCtrl.dll and b/third_party/KInput/KInput/KInputCtrl/bin/Release/KInputCtrl.dll differ
diff --git a/third_party/KInput/KInput/KInputCtrl/obj/Release/RemoteProcFinder.o b/third_party/KInput/KInput/KInputCtrl/obj/Release/RemoteProcFinder.o
index 71a4c66..39ad0e5 100644
Binary files a/third_party/KInput/KInput/KInputCtrl/obj/Release/RemoteProcFinder.o and b/third_party/KInput/KInput/KInputCtrl/obj/Release/RemoteProcFinder.o differ
diff --git a/third_party/KInput/KInput/KInputCtrl/obj/Release/main.o b/third_party/KInput/KInput/KInputCtrl/obj/Release/main.o
index b5553f6..fbf1c98 100644
Binary files a/third_party/KInput/KInput/KInputCtrl/obj/Release/main.o and b/third_party/KInput/KInput/KInputCtrl/obj/Release/main.o differ