From 59d9ec5b4abcc86bd389e69e2f47d3cb022d2edc Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Sun, 11 Oct 2026 00:33:10 -0700 Subject: [PATCH] Persist applet cookies to /config/saves The JSObject stand-in kept cookies in memory, so saves were lost when the game restarted. Write them to a file the session manager can sync, with the policy granting only the agent jar access to that directory. --- streaming/streamer/README.md | 2 +- .../netscape/javascript/Document.java | 72 +++++++++++++++---- .../root/opt/flashpoint/applet.policy | 6 ++ 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/streaming/streamer/README.md b/streaming/streamer/README.md index 0dfdb7d..556078c 100644 --- a/streaming/streamer/README.md +++ b/streaming/streamer/README.md @@ -37,6 +37,6 @@ Selkies restarts the game when it exits (`RESTART_APP`). `appletviewer` lacks two things applets got from the browser's Java plugin, so the image adds them: a `netscape.javascript.JSObject` stand-in whose window has -a working `document.cookie`, and a policy allowing HTTP requests through the +a `document.cookie` that persists to `/config/saves/cookies.txt`, and a policy allowing HTTP requests through the game server's proxy. The same jar (`applet-support/`) is loaded as a Java agent that removes appletviewer's menu bar and status line. diff --git a/streaming/streamer/applet-support/netscape/javascript/Document.java b/streaming/streamer/applet-support/netscape/javascript/Document.java index 54e4973..b80c080 100644 --- a/streaming/streamer/applet-support/netscape/javascript/Document.java +++ b/streaming/streamer/applet-support/netscape/javascript/Document.java @@ -1,25 +1,37 @@ package netscape.javascript; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; -/** A document whose cookie member behaves like document.cookie in a browser. */ +/** + * A document whose cookie member behaves like document.cookie in a browser. + * Cookies persist to ~/saves/cookies.txt, one name=value pair per line. + */ class Document extends JSObject { + private final File file = AccessController.doPrivileged( + (PrivilegedAction) () -> new File(System.getProperty("user.home"), "saves/cookies.txt")); private final Map cookies = new LinkedHashMap(); + Document() { + for (String line : AccessController.doPrivileged((PrivilegedAction>) this::read)) { + put(line); + } + } + @Override public Object getMember(String name) throws JSException { if (!name.equals("cookie")) { return super.getMember(name); } - StringBuilder sb = new StringBuilder(); - for (Map.Entry cookie : cookies.entrySet()) { - if (sb.length() > 0) { - sb.append("; "); - } - sb.append(cookie.getKey()).append('=').append(cookie.getValue()); - } - return sb.toString(); + return String.join("; ", lines()); } @Override @@ -28,10 +40,44 @@ class Document extends JSObject { super.setMember(name, value); return; } - String pair = String.valueOf(value).split(";", 2)[0]; - int eq = pair.indexOf('='); - if (eq > 0) { - cookies.put(pair.substring(0, eq).trim(), pair.substring(eq + 1).trim()); + if (put(String.valueOf(value).split(";", 2)[0])) { + AccessController.doPrivileged((PrivilegedAction) this::write); } } + + private boolean put(String pair) { + int eq = pair.indexOf('='); + if (eq <= 0) { + return false; + } + cookies.put(pair.substring(0, eq).trim(), pair.substring(eq + 1).trim()); + return true; + } + + private List lines() { + List lines = new ArrayList(); + for (Map.Entry cookie : cookies.entrySet()) { + lines.add(cookie.getKey() + "=" + cookie.getValue()); + } + return lines; + } + + private List read() { + try { + return file.isFile() ? Files.readAllLines(file.toPath(), StandardCharsets.UTF_8) : new ArrayList(); + } catch (IOException e) { + System.err.println("Failed to read " + file + ": " + e); + return new ArrayList(); + } + } + + private Void write() { + try { + file.getParentFile().mkdirs(); + Files.write(file.toPath(), lines(), StandardCharsets.UTF_8); + } catch (IOException e) { + System.err.println("Failed to write " + file + ": " + e); + } + return null; + } } diff --git a/streaming/streamer/root/opt/flashpoint/applet.policy b/streaming/streamer/root/opt/flashpoint/applet.policy index fb38806..506363d 100644 --- a/streaming/streamer/root/opt/flashpoint/applet.policy +++ b/streaming/streamer/root/opt/flashpoint/applet.policy @@ -1,3 +1,9 @@ grant { permission java.net.URLPermission "http:*", "*:*"; }; + +grant codeBase "file:/opt/flashpoint/applet-support.jar" { + permission java.util.PropertyPermission "user.home", "read"; + permission java.io.FilePermission "${user.home}${/}saves", "read,write"; + permission java.io.FilePermission "${user.home}${/}saves${/}-", "read,write"; +};