Build offline OpenPrestige release bundles

Rename the injector and package it with an embedded payload for Windows, Linux, and macOS x64/ARM64. Add a core-only ImGui native build, multi-platform CI and release packaging, and shared per-user storage with legacy config migration.
This commit is contained in:
bbldrizzy committed 2026-11-03 18:45:13 +00:00
1 parent 5152e6e0a6
commit 2d6b2a3cd1
37 files changed
+1284 -451

No files matched your search

@@ -3,6 +3,7 @@ package dev.zprestige.prestige.ui;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import dev.zprestige.prestige.client.AppPaths;
import dev.zprestige.prestige.client.Prestige;
import java.io.IOException;
@@ -17,7 +18,7 @@ import java.util.Locale;
import java.util.stream.Stream;
/**
* Local (offline) backend of the Configs page. Every config is one JSON file in {@code <gameDir>/prestige/configs/}:
* Local (offline) backend of the Configs page. Every config is one JSON file in the shared OpenPrestige data directory:
* the card metadata plus the client's own snapshot ({@code ConfigManager.saveToJson()}), which is applied again with
* {@code ConfigManager.loadFromJson(String)}. "Public configs" lists the configs saved with Public visibility, "Personal configs"
* lists all of them; Create saves the current module/HUD state, Update overwrites it, Delete removes the file.
@@ -45,6 +46,10 @@ public final class LocalConfigs {
private static String lastQuery = null;
static Path dir() {
return AppPaths.configs().resolve("presets");
}
private static Path legacyDir() {
Path base;
try { base = net.fabricmc.loader.api.FabricLoader.getInstance().getGameDir(); } catch (Throwable t) { base = Path.of("."); }
return base.resolve("prestige").resolve("configs");
@@ -86,21 +91,61 @@ public final class LocalConfigs {
for (Path p : (Iterable<Path>) s.filter(f -> f.getFileName().toString().endsWith(".json"))::iterator) {
try {
Entry e = GSON.fromJson(Files.readString(p, StandardCharsets.UTF_8), Entry.class);
if (e == null || e.name == null || e.name.isBlank()) continue;
if (e.tags == null) e.tags = new ArrayList<>();
e.tags.removeIf(java.util.Objects::isNull);
if (e.desc == null) e.desc = "";
if (e.author == null) e.author = "";
if (e.date == null) e.date = "";
ALL.add(e);
if (normalize(e)) ALL.add(e);
} catch (Throwable t) { System.out.println("[prestige-ui] config " + p.getFileName() + ": " + t); }
}
}
importLegacy(d);
} catch (IOException e) { System.out.println("[prestige-ui] configs dir: " + e); }
ALL.sort(Comparator.comparingInt((Entry e) -> e.id).reversed());
rebuild();
}
private static boolean normalize(Entry entry) {
if (entry == null || entry.name == null || entry.name.isBlank()) return false;
if (entry.tags == null) entry.tags = new ArrayList<>();
entry.tags.removeIf(java.util.Objects::isNull);
if (entry.desc == null) entry.desc = "";
if (entry.author == null) entry.author = "";
if (entry.date == null) entry.date = "";
return true;
}
private static JsonObject configWithoutId(Entry entry) {
JsonObject config = GSON.toJsonTree(entry).getAsJsonObject();
config.remove("id");
return config;
}
private static void importLegacy(Path directory) throws IOException {
Path legacy = legacyDir().toAbsolutePath().normalize();
if (!Files.isDirectory(legacy)) return;
String key = java.util.UUID.nameUUIDFromBytes(legacy.toString().getBytes(StandardCharsets.UTF_8)).toString();
Path marker = directory.resolve(".imported-" + key);
if (Files.exists(marker)) return;
try (Stream<Path> files = Files.list(legacy)) {
for (Path path : files.filter(p -> p.getFileName().toString().endsWith(".json")).toList()) {
Entry entry;
try {
entry = GSON.fromJson(Files.readString(path, StandardCharsets.UTF_8), Entry.class);
} catch (RuntimeException error) {
System.out.println("[prestige-ui] could not import " + path.getFileName() + ": " + error);
continue;
}
if (!normalize(entry)) continue;
JsonObject content = configWithoutId(entry);
if (ALL.stream().anyMatch(existing -> configWithoutId(existing).equals(content))) continue;
int originalId = entry.id;
if (ALL.stream().anyMatch(existing -> existing.id == originalId)) {
entry.id = ALL.stream().mapToInt(existing -> existing.id).max().orElse(0) + 1;
}
Files.writeString(file(entry), GSON.toJson(entry), StandardCharsets.UTF_8, java.nio.file.StandardOpenOption.CREATE_NEW);
ALL.add(entry);
}
}
Files.writeString(marker, "");
}
private static Path file(Entry e) { return dir().resolve(e.id + "_" + e.name.replaceAll("[^A-Za-z0-9_.-]", "_") + ".json"); }
private static void write(Entry e) {