diff --git a/bridge/src/main/java/net/rustexremake/api/Rsx.java b/bridge/src/main/java/net/rustexremake/api/Rsx.java index 3b36090..1aa8d16 100644 --- a/bridge/src/main/java/net/rustexremake/api/Rsx.java +++ b/bridge/src/main/java/net/rustexremake/api/Rsx.java @@ -385,6 +385,16 @@ public final class Rsx { return h.scale(); } + /** Screen width in pixels, 0 when the game did not say. */ + public int width() { + return h == null ? 0 : h.width(); + } + + /** Screen height in pixels, 0 when the game did not say. */ + public int height() { + return h == null ? 0 : h.height(); + } + /** A solid rectangle; alpha 0..1. */ public void rect(float x, float y, float w, float height, int rgb, float alpha) { diff --git a/bridge/src/main/java/rsx/Hud.java b/bridge/src/main/java/rsx/Hud.java index 112d3f0..5bf16ea 100644 --- a/bridge/src/main/java/rsx/Hud.java +++ b/bridge/src/main/java/rsx/Hud.java @@ -41,18 +41,14 @@ public final class Hud { return Ui.scale(); } - /** - * Screen size in HUD units, or 0 when it cannot be read. - * - * The context does not expose it by name, so this stays 0 until a build - * gives us a way; callers fall back to the game's own scale factor. - */ + /** Screen width, 0 when the game did not say. */ public int width() { - return 0; + return HudHook.width(); } + /** Screen height, 0 when the game did not say. */ public int height() { - return 0; + return HudHook.height(); } /** A solid rectangle. Colour is 0xRRGGBB, alpha 0..1. */ diff --git a/bridge/src/main/java/rsx/HudHook.java b/bridge/src/main/java/rsx/HudHook.java index 874347a..52b78b8 100644 --- a/bridge/src/main/java/rsx/HudHook.java +++ b/bridge/src/main/java/rsx/HudHook.java @@ -142,6 +142,52 @@ public final class HudHook { } } + /** + * Screen size, read off the context. + * + * The context offers two no-argument int getters and nothing names them, so + * they are told apart by what they can only be: on any window the larger one + * is the width. Getting this wrong only mirrors the panel, it cannot break + * the frame. + */ + static int width() { + int[] s = size(); + return s == null ? 0 : Math.max(s[0], s[1]); + } + + static int height() { + int[] s = size(); + return s == null ? 0 : Math.min(s[0], s[1]); + } + + private static int[] size; + + private static synchronized int[] size() { + if (size != null || context == null) { + return size; + } + java.util.List found = new ArrayList(); + for (Class c = context.getClass(); c != null; c = c.getSuperclass()) { + for (Method m : c.getDeclaredMethods()) { + if (m.getParameterTypes().length != 0 + || m.getReturnType() != int.class) { + continue; + } + try { + m.setAccessible(true); + found.add(Integer.valueOf(((Integer) m.invoke(context)) + .intValue())); + } catch (Throwable ignored) { + } + } + } + if (found.size() >= 2) { + size = new int[] {found.get(0).intValue(), found.get(1).intValue()}; + Log.info("HUD: screen " + width() + "x" + height()); + } + return size; + } + /** True while a mod callback is running, i.e. Rsx.hud() is meaningful. */ public static boolean inside() { return context != null; diff --git a/bridge/src/test/fakeclient/fakectx.java b/bridge/src/test/fakeclient/fakectx.java index 76cbfa7..9195c09 100644 --- a/bridge/src/test/fakeclient/fakectx.java +++ b/bridge/src/test/fakeclient/fakectx.java @@ -1,11 +1,20 @@ // Test stand-in for the game's HUD render context. // -// One method takes a draw command. The bridge finds it by shape rather than by -// name, which is what makes the binding survive the next re-obfuscation. +// One method takes a draw command, and two no-argument int getters give the +// screen size. The bridge finds the submit method by shape and tells the two +// getters apart by which is larger, so the names here do not matter. public class fakectx { + public int screenWidth() { + return 1920; + } + + public int screenHeight() { + return 1080; + } + public void accept(fakecmd command) { fakebatch.submit(command); } -} +} \ No newline at end of file diff --git a/bridge/src/test/mod/HudMod.java b/bridge/src/test/mod/HudMod.java index da0672b..537d6c6 100644 --- a/bridge/src/test/mod/HudMod.java +++ b/bridge/src/test/mod/HudMod.java @@ -18,6 +18,8 @@ public class HudMod implements RsxMod { public static int glyphs; private final Runnable draw = new Runnable() { + private int reported; + @Override public void run() { frames++; @@ -25,6 +27,11 @@ public class HudMod implements RsxMod { if (hud == null || !hud.available()) { return; } + if (reported == 0 && hud.width() > 0) { + reported = 1; + Rsx.info("HudMod screen: " + hud.width() + "x" + hud.height() + + " scale=" + hud.scale()); + } hud.rect(8, 8, 40, 12, 0x20201C, 0.85f); rects++; hud.text(8, 8, "42 ms", 0xECE3DB, 1f); diff --git a/bridge/src/test/python/run_test.py b/bridge/src/test/python/run_test.py index dd7c01f..f8de0ac 100644 --- a/bridge/src/test/python/run_test.py +++ b/bridge/src/test/python/run_test.py @@ -288,6 +288,7 @@ def main(): ("panel drew a rectangle and a label", "color=20201c" in out and "textures/font/ascii.png" in out and "color=ece3db" in out), + ("screen size read from the context", "HudMod screen: 1920x1080" in out), ("panel stops when the game hides the HUD", "HUD hidden (what F3 does)" in out and "frame 0 commands=0" in out and "frame 2 commands=0" in out),