Skip to content

Commit 0242000

Browse files
committed
Include bundled updater and config versioning
Bundle a standalone updater JAR into the client and add config/manifest-driven version handling. Build scripts (build.sh/build.ps1) now create Progressive-Java-Updater.jar and fold it into the client classes; CI stamps release versions from tag refs and pom version bumped to 1.7. ClientConfig now records and exposes a version label (writes/updates config.json, reads manifest), and GLRenderer shows a static version text instead of the old in‑client updater UI. ClientUpdater was refactored to extract/ensure the updater JAR beside the running client, consult the config.json manifest to avoid re-downloading identical releases, and launch the updater in a separate JVM with a reconstructed restart command. Added a new UpdateHelper class (GUI + CLI) that performs the actual replacement/restart logic when invoked by the updater JAR. Various run/build wrappers updated to require/expose the updater artifact and to report build outputs.
1 parent 88d5bf3 commit 0242000

11 files changed

Lines changed: 744 additions & 141 deletions

File tree

.github/workflows/build-jar.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ jobs:
2222
java-version: "17"
2323
cache: maven
2424

25+
# For tag builds, stamp the project version from the tag (v1.7 -> 1.7) so the jar's
26+
# Implementation-Version matches the release. Without this the manifest keeps the
27+
# hardcoded pom version and the updater re-downloads the same release forever.
28+
- name: Set release version from tag
29+
if: startsWith(github.ref, 'refs/tags/v')
30+
run: mvn --batch-mode versions:set -DnewVersion="${GITHUB_REF_NAME#v}" -DgenerateBackupPoms=false
31+
2532
- name: Build
2633
run: mvn --batch-mode clean package
2734

build.ps1

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,38 @@ Remove-Item target/classes/META-INF/*.SF -Force -ErrorAction SilentlyContinue
4444
Remove-Item target/classes/META-INF/*.DSA -Force -ErrorAction SilentlyContinue
4545
Remove-Item target/classes/META-INF/*.RSA -Force -ErrorAction SilentlyContinue
4646

47-
$clientVersion = if ($env:CLIENT_VERSION) { $env:CLIENT_VERSION.TrimStart("v") } else { "1.6" }
47+
$clientVersion = if ($env:CLIENT_VERSION) { $env:CLIENT_VERSION.TrimStart("v") } else { "1.7" }
48+
@"
49+
{
50+
"version": "$clientVersion",
51+
"web_host": "localhost",
52+
"web_port": 80,
53+
"game_port": 43594
54+
}
55+
"@ | Set-Content -Encoding UTF8 target/config.json
4856
@"
4957
Manifest-Version: 1.0
5058
Implementation-Version: $clientVersion
5159
Build-Time: $((Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))
5260
5361
"@ | Set-Content -Encoding ascii target/manifest.mf
5462

63+
# Build the updater jar first, then fold it into the client classes so it ships
64+
# *inside* the client jar. At runtime the client extracts it back beside itself.
65+
jar --create --file target/Progressive-Java-Updater.jar --main-class com.gradwahl.rs254.update.UpdateHelper -C target/classes com/gradwahl/rs254/update
66+
if ($LASTEXITCODE -ne 0) {
67+
throw "updater jar failed with exit code $LASTEXITCODE"
68+
}
69+
Copy-Item target/Progressive-Java-Updater.jar target/classes/Progressive-Java-Updater.jar -Force
70+
5571
jar --create --file target/Progressive-Java-Client.jar --main-class com.gradwahl.rs254.Main --manifest target/manifest.mf -C target/classes .
5672
if ($LASTEXITCODE -ne 0) {
5773
throw "jar failed with exit code $LASTEXITCODE. Close any running client and rebuild."
5874
}
5975
Remove-Item target/manifest.mf
6076

6177
Write-Host "Build complete: target/Progressive-Java-Client.jar"
78+
Write-Host "Build complete: target/Progressive-Java-Updater.jar"
6279

6380
# Wrap the JAR in a single .exe with the custom icon using Launch4j.
6481
$launch4jc = "C:\Program Files (x86)\Launch4j\launch4jc.exe"

build.sh

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,26 @@ rm -f target/classes/META-INF/MANIFEST.MF
3535
rm -f target/classes/META-INF/*.SF target/classes/META-INF/*.DSA target/classes/META-INF/*.RSA
3636

3737
BUILD_TIME="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
38-
CLIENT_VERSION="${CLIENT_VERSION:-1.6}"
38+
CLIENT_VERSION="${CLIENT_VERSION:-1.7}"
3939
CLIENT_VERSION="${CLIENT_VERSION#v}"
40+
cat > target/config.json <<EOF
41+
{
42+
"version": "$CLIENT_VERSION",
43+
"web_host": "localhost",
44+
"web_port": 80,
45+
"game_port": 43594
46+
}
47+
EOF
4048
printf 'Manifest-Version: 1.0\nImplementation-Version: %s\nBuild-Time: %s\n\n' "$CLIENT_VERSION" "$BUILD_TIME" > target/manifest.mf
4149

50+
# Build the updater jar first, then fold it into the client classes so it ships
51+
# *inside* the client jar. At runtime the client extracts it back beside itself.
52+
jar --create --file target/Progressive-Java-Updater.jar \
53+
--main-class com.gradwahl.rs254.update.UpdateHelper \
54+
-C target/classes com/gradwahl/rs254/update
55+
56+
cp target/Progressive-Java-Updater.jar target/classes/Progressive-Java-Updater.jar
57+
4258
jar --create --file target/Progressive-Java-Client.jar \
4359
--main-class com.gradwahl.rs254.Main \
4460
--manifest target/manifest.mf \
@@ -47,4 +63,5 @@ jar --create --file target/Progressive-Java-Client.jar \
4763
rm target/manifest.mf
4864

4965
echo "Build complete: target/Progressive-Java-Client.jar"
66+
echo "Build complete: target/Progressive-Java-Updater.jar"
5067
echo "Run with: ./run.sh"

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>com.gradwahl</groupId>
77
<artifactId>Progressive-Java-Client</artifactId>
8-
<version>1.6</version>
8+
<version>1.7</version>
99
<name>Progressive Java Client</name>
1010

1111
<properties>

run.bat

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ cd /d "%~dp0"
44
set "SCRIPT_DIR=%~dp0"
55
set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%"
66
if not exist target\Progressive-Java-Client.jar call build.bat
7+
if not exist target\Progressive-Java-Updater.jar call build.bat
78

89
if not exist "%SCRIPT_DIR%\logs" mkdir "%SCRIPT_DIR%\logs"
910

run.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ if [ "$MISSING" -eq 1 ]; then
5858
REBUILD=1
5959
fi
6060

61-
if [ ! -f target/Progressive-Java-Client.jar ] || [ "$REBUILD" -eq 1 ]; then
61+
if [ ! -f target/Progressive-Java-Client.jar ] || [ ! -f target/Progressive-Java-Updater.jar ] || [ "$REBUILD" -eq 1 ]; then
6262
bash build.sh
6363
fi
6464

src/main/java/com/gradwahl/rs254/ClientConfig.java

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,25 @@
33
import java.io.*;
44
import java.nio.charset.StandardCharsets;
55
import java.nio.file.Files;
6+
import java.util.jar.Attributes;
7+
import java.util.jar.Manifest;
68
import java.util.regex.Matcher;
79
import java.util.regex.Pattern;
810

9-
public record ClientConfig(String host, int httpPort, int gamePort, boolean secure, int revision, String cacheDir, String dbPath) {
11+
public record ClientConfig(String host, int httpPort, int gamePort, boolean secure, int revision,
12+
String cacheDir, String dbPath, String version) {
1013

1114
private static final String CONFIG_FILE = "config.json";
1215

1316
public static ClientConfig load() {
1417
File configFile = resolveConfigFile();
1518
boolean firstRun = !configFile.exists();
19+
String version = currentVersionLabel();
1620

1721
if (firstRun) {
1822
String defaultConfig =
1923
"{\n" +
24+
" \"version\": \"" + escapeJson(version) + "\",\n" +
2025
" \"web_host\": \"localhost\",\n" +
2126
" \"web_port\": 80,\n" +
2227
" \"game_port\": 43594\n" +
@@ -30,11 +35,33 @@ public static ClientConfig load() {
3035
}
3136
} else {
3237
System.out.println("[Config] Loaded config from: " + configFile.getAbsolutePath());
38+
updateVersionField(configFile, version);
3339
}
3440

3541
return parseFile(configFile);
3642
}
3743

44+
public static String currentVersionLabel() {
45+
try {
46+
String version = ClientConfig.class.getPackage().getImplementationVersion();
47+
if (version != null && !version.isBlank()) {
48+
return version;
49+
}
50+
var manifestUrl = ClientConfig.class.getResource("/META-INF/MANIFEST.MF");
51+
if (manifestUrl != null) {
52+
try (InputStream in = manifestUrl.openStream()) {
53+
Attributes attrs = new Manifest(in).getMainAttributes();
54+
version = attrs.getValue("Implementation-Version");
55+
if (version != null && !version.isBlank()) {
56+
return version;
57+
}
58+
}
59+
}
60+
} catch (Exception ignored) {
61+
}
62+
return "dev";
63+
}
64+
3865
private static File resolveConfigFile() {
3966
// Place config next to the JAR, falling back to the working directory
4067
try {
@@ -54,10 +81,12 @@ private static ClientConfig parseFile(File file) {
5481
int revision = 254;
5582
String cacheDir = "cache";
5683
String dbPath = "";
84+
String version = currentVersionLabel();
5785

5886
if (file.exists()) {
5987
try {
6088
String json = Files.readString(file.toPath(), StandardCharsets.UTF_8);
89+
version = readString(json, "version", version);
6190
host = readString(json, "web_host", host);
6291
httpPort = readInt(json, "web_port", httpPort);
6392
gamePort = readInt(json, "game_port", gamePort);
@@ -76,7 +105,30 @@ private static ClientConfig parseFile(File file) {
76105
cacheDir = System.getProperty("rs254.cacheDir", cacheDir);
77106
dbPath = System.getProperty("rs254.dbPath", dbPath);
78107

79-
return new ClientConfig(host, httpPort, gamePort, secure, revision, cacheDir, dbPath);
108+
return new ClientConfig(host, httpPort, gamePort, secure, revision, cacheDir, dbPath, version);
109+
}
110+
111+
private static void updateVersionField(File file, String version) {
112+
try {
113+
String json = Files.readString(file.toPath(), StandardCharsets.UTF_8);
114+
String escaped = escapeJson(version);
115+
String updated;
116+
if (Pattern.compile("\"version\"\\s*:").matcher(json).find()) {
117+
updated = json.replaceFirst("\"version\"\\s*:\\s*\"((?:[^\\\\\"]|\\\\.)*)\"",
118+
"\"version\": \"" + Matcher.quoteReplacement(escaped) + "\"");
119+
} else {
120+
int objectStart = json.indexOf('{');
121+
if (objectStart < 0) return;
122+
updated = json.substring(0, objectStart + 1)
123+
+ "\n \"version\": \"" + escaped + "\","
124+
+ json.substring(objectStart + 1);
125+
}
126+
if (!updated.equals(json)) {
127+
Files.writeString(file.toPath(), updated, StandardCharsets.UTF_8);
128+
}
129+
} catch (IOException e) {
130+
System.err.println("[Config] Warning: could not update version in " + file + ": " + e.getMessage());
131+
}
80132
}
81133

82134
private static String readString(String json, String key, String defaultValue) {
@@ -96,6 +148,10 @@ private static int readInt(String json, String key, int defaultValue) {
96148
return defaultValue;
97149
}
98150

151+
private static String escapeJson(String value) {
152+
return value.replace("\\", "\\\\").replace("\"", "\\\"");
153+
}
154+
99155
/** @deprecated Use {@link #load()} instead. */
100156
@Deprecated
101157
public static ClientConfig fromSystemProperties() {

src/main/java/com/gradwahl/rs254/Main.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public static void main(String[] args) throws Exception {
1717
setupErrorLogging();
1818
applyEarlyGraphicsProperties();
1919
relaunchJarWithOneGbHeapIfNeeded(args);
20+
com.gradwahl.rs254.update.ClientUpdater.ensureUpdaterExtracted();
2021
ClientDebugger.enable();
2122

2223
ClientConfig config = ClientConfig.load();

src/main/java/com/gradwahl/rs254/gl/GLRenderer.java

Lines changed: 5 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@
4444
import javax.swing.text.html.HTMLDocument;
4545
import javax.swing.text.html.HTMLEditorKit;
4646

47+
import com.gradwahl.rs254.ClientConfig;
4748
import com.gradwahl.rs254.discord.DiscordRichPresence;
48-
import com.gradwahl.rs254.update.ClientUpdater;
4949

5050
import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks;
5151
import static org.lwjgl.glfw.GLFW.*;
@@ -507,16 +507,7 @@ void main() {
507507
private boolean settingsFullscreen = false;
508508
private boolean settingsAfkDropdownOpen;
509509
private int settingsAfkIndex = SETTINGS_PREFS.getInt("afkIndex", 0);
510-
private enum UpdateButtonState { OUTDATED, CHECKING, READY, UPDATED, APPLYING }
511-
private volatile UpdateButtonState updateButtonState = UpdateButtonState.OUTDATED;
512-
private volatile String updateStatusText = "Current: " + ClientUpdater.currentVersionLabel();
513-
private volatile ClientUpdater.UpdateInfo updateInfo;
514-
private final java.util.concurrent.ExecutorService updateExecutor =
515-
java.util.concurrent.Executors.newSingleThreadExecutor(r -> {
516-
Thread t = new Thread(r, "client-updater");
517-
t.setDaemon(true);
518-
return t;
519-
});
510+
private final String clientVersionText = "Version: " + ClientConfig.currentVersionLabel();
520511

521512
// XP session tracking — updated by Client when XP packets arrive
522513
public static final long[] xpSessionGains = new long[25];
@@ -896,7 +887,6 @@ public void destroy() {
896887
if (sidebarNativeDirect != null) MemoryUtil.memFree(sidebarNativeDirect);
897888
hiscoresFetcher.shutdownNow();
898889
DISCORD_RPC.disconnect();
899-
updateExecutor.shutdownNow();
900890
glfwFreeCallbacks(window);
901891
glfwDestroyWindow(window);
902892
glfwTerminate();
@@ -2805,7 +2795,7 @@ private void drawSettingsPanel(int x) {
28052795
y = drawSettingsToggleRow(x, y, "60 Fps Mode", sidebarFpsEnabled);
28062796
y = drawSettingsToggleRow(x, y, "Fullscreen Mode", settingsFullscreen);
28072797
y += 4;
2808-
drawUpdateButton(x, y);
2798+
drawClientVersionText(x, y);
28092799
}
28102800

28112801
private void loadSettings() {
@@ -2838,36 +2828,9 @@ private int drawSettingsToggleRow(int x, int y, String text, boolean enabled) {
28382828
return y + 20;
28392829
}
28402830

2841-
private void drawUpdateButton(int x, int y) {
2831+
private void drawClientVersionText(int x, int y) {
28422832
int panelW = sidebarPanelW();
2843-
int buttonX = x + 16;
2844-
int buttonW = panelW - 32;
2845-
int color = updateButtonColor();
2846-
fillUiRect(buttonX, y, buttonW, 18, 0xFF202020);
2847-
fillUiRect(buttonX, y, buttonW, 1, color);
2848-
fillUiRect(buttonX, y + 17, buttonW, 1, 0xFF111111);
2849-
fillUiRect(buttonX, y, 1, 18, 0xFF4A4A4A);
2850-
fillUiRect(buttonX + buttonW - 1, y, 1, 18, 0xFF111111);
2851-
drawUiTextCentered(updateButtonText(), buttonX, y, buttonW, 18, 0, color);
2852-
drawUiTextFittedFull(updateStatusText, buttonX, y + 23, buttonW, 0, 0xFF999999);
2853-
}
2854-
2855-
private int updateButtonColor() {
2856-
return switch (updateButtonState) {
2857-
case READY, CHECKING, APPLYING -> 0xFF4AA3FF;
2858-
case UPDATED -> 0xFF43D36B;
2859-
case OUTDATED -> 0xFFE05252;
2860-
};
2861-
}
2862-
2863-
private String updateButtonText() {
2864-
return switch (updateButtonState) {
2865-
case CHECKING -> "CHECKING...";
2866-
case READY -> "READY TO UPDATE";
2867-
case UPDATED -> "UPDATED TO LATEST";
2868-
case APPLYING -> "APPLYING UPDATE...";
2869-
case OUTDATED -> "CHECK FOR UPDATES";
2870-
};
2833+
drawUiTextFittedFull(clientVersionText, x + 16, y + 5, panelW - 32, 0, 0xFF999999);
28712834
}
28722835

28732836
private void drawSelectBox(int x, int y, int w, String text, boolean open) {
@@ -3768,73 +3731,13 @@ private void clickSettingsPanel(int x, int y) {
37683731
if (toggleHit(px, rowY, x, y)) { setFps60(!sidebarFpsEnabled); return; }
37693732
rowY += 20;
37703733
if (toggleHit(px, rowY, x, y)) { toggleFullscreen(); return; }
3771-
rowY += 24;
3772-
if (updateButtonHit(px, rowY, x, y)) clickUpdateButton();
37733734
}
37743735

37753736
private boolean toggleHit(int px, int rowY, int mouseX, int mouseY) {
37763737
return mouseX >= px + 8 && mouseX < px + sidebarPanelW() - 8
37773738
&& mouseY >= rowY && mouseY < rowY + 20;
37783739
}
37793740

3780-
private boolean updateButtonHit(int px, int rowY, int mouseX, int mouseY) {
3781-
return mouseX >= px + 16 && mouseX < px + sidebarPanelW() - 16
3782-
&& mouseY >= rowY && mouseY < rowY + 18;
3783-
}
3784-
3785-
private void clickUpdateButton() {
3786-
if (updateButtonState == UpdateButtonState.CHECKING
3787-
|| updateButtonState == UpdateButtonState.APPLYING) {
3788-
return;
3789-
}
3790-
if (updateButtonState == UpdateButtonState.READY && updateInfo != null) {
3791-
applyUpdate();
3792-
} else {
3793-
checkForUpdates();
3794-
}
3795-
}
3796-
3797-
private void checkForUpdates() {
3798-
updateButtonState = UpdateButtonState.CHECKING;
3799-
updateStatusText = "Contacting GitHub releases...";
3800-
updateExecutor.execute(() -> {
3801-
try {
3802-
ClientUpdater.UpdateInfo latest = ClientUpdater.checkLatest();
3803-
updateInfo = latest;
3804-
if (latest.updateAvailable()) {
3805-
updateButtonState = UpdateButtonState.READY;
3806-
updateStatusText = "Latest: " + latest.tagName() + " (" + latest.assetName() + ")";
3807-
} else {
3808-
updateButtonState = UpdateButtonState.UPDATED;
3809-
updateStatusText = "Latest: " + latest.tagName();
3810-
}
3811-
} catch (Exception e) {
3812-
updateInfo = null;
3813-
updateButtonState = UpdateButtonState.OUTDATED;
3814-
updateStatusText = "Update check failed";
3815-
System.err.println("[Updater] Check failed: " + e);
3816-
e.printStackTrace(System.err);
3817-
}
3818-
});
3819-
}
3820-
3821-
private void applyUpdate() {
3822-
ClientUpdater.UpdateInfo info = updateInfo;
3823-
if (info == null) return;
3824-
updateButtonState = UpdateButtonState.APPLYING;
3825-
updateStatusText = "Downloading " + info.assetName();
3826-
updateExecutor.execute(() -> {
3827-
try {
3828-
ClientUpdater.apply(info);
3829-
} catch (Exception e) {
3830-
updateButtonState = UpdateButtonState.READY;
3831-
updateStatusText = "Update failed";
3832-
System.err.println("[Updater] Apply failed: " + e);
3833-
e.printStackTrace(System.err);
3834-
}
3835-
});
3836-
}
3837-
38383741
private void setAfkIndex(int index) {
38393742
settingsAfkIndex = Math.max(0, Math.min(AFK_LABELS.length - 1, index));
38403743
afkTimeoutCycles = AFK_CYCLES[settingsAfkIndex];

0 commit comments

Comments
 (0)