From 92d094e64cc515fdc8bdbedca284f4d01fc2eaf8 Mon Sep 17 00:00:00 2001 From: JustABiologist Date: Tue, 12 May 2026 14:47:43 +0200 Subject: [PATCH 1/3] Add DayZ workshop mod updates --- .../client/configs/UpdaterConfig.java | 4 + .../tasks/updater/mods/DayZWorkshopMod.java | 89 +++++++++++++++++++ .../tasks/updater/mods/TaskModsUpdater.java | 63 ++++++++++++- .../autoplug/client/utils/SteamCMD.java | 61 ++++++++++++- .../updater/mods/DayZWorkshopModTest.java | 68 ++++++++++++++ .../autoplug/client/utils/SteamCMDTest.java | 11 ++- 6 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopMod.java create mode 100644 src/test/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopModTest.java diff --git a/src/main/java/com/osiris/autoplug/client/configs/UpdaterConfig.java b/src/main/java/com/osiris/autoplug/client/configs/UpdaterConfig.java index 45437a41..7729e889 100644 --- a/src/main/java/com/osiris/autoplug/client/configs/UpdaterConfig.java +++ b/src/main/java/com/osiris/autoplug/client/configs/UpdaterConfig.java @@ -64,6 +64,7 @@ public class UpdaterConfig extends MyYaml { public YamlSection mods_updater_version; public YamlSection mods_updater_async; public YamlSection mods_update_check_name_for_mod_loader; + public YamlSection mods_updater_dayz_workshop_app_id; public UpdaterConfig() throws IOException, DuplicateKeyException, YamlReaderException, IllegalListException, NotLoadedException, IllegalKeyException, YamlWriterException { @@ -230,6 +231,9 @@ public UpdaterConfig() throws IOException, DuplicateKeyException, YamlReaderExce mods_update_check_name_for_mod_loader = put(name, "mods-updater", "check-name-for-mod-loader").setDefValues("false").setComments( "Only relevant for determining if a curseforge mod release is forge or fabric.", "If enabled additionally checks the mod name to see if it contains fabric or forge."); + mods_updater_dayz_workshop_app_id = put(name, "mods-updater", "dayz-workshop-app-id").setDefValues("221100").setComments( + "Steam workshop app-id used for DayZ mods.", + "When the mods path contains DayZ mod folders with meta.cpp files, AutoPlug reads their publishedid and updates them through SteamCMD."); save(); unlockFile(); diff --git a/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopMod.java b/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopMod.java new file mode 100644 index 00000000..9523742e --- /dev/null +++ b/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopMod.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 Osiris-Team. + * All rights reserved. + * + * This software is copyrighted work, licensed under the terms + * of the MIT-License. Consult the "LICENSE" file for details. + */ + +package com.osiris.autoplug.client.tasks.updater.mods; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +public class DayZWorkshopMod { + private final File directory; + private final String name; + private final String publishedId; + + public DayZWorkshopMod(File directory, String name, String publishedId) { + this.directory = directory; + this.name = name; + this.publishedId = publishedId; + } + + public File getDirectory() { + return directory; + } + + public String getName() { + return name; + } + + public String getPublishedId() { + return publishedId; + } + + @NotNull + public static List findIn(File dir) throws IOException { + if (!dir.exists()) throw new FileNotFoundException("Directory does not exist: " + dir); + List mods = new ArrayList<>(); + File[] files = dir.listFiles(); + if (files == null) return mods; + Arrays.sort(files, Comparator.comparing(File::getName)); + for (File file : files) { + if (!file.isDirectory()) continue; + File metaFile = new File(file, "meta.cpp"); + if (metaFile.exists()) + mods.add(readFromMeta(file, metaFile)); + } + return mods; + } + + static DayZWorkshopMod readFromMeta(File modDir, File metaFile) throws IOException { + String name = modDir.getName(); + String publishedId = null; + for (String line : Files.readAllLines(metaFile.toPath(), StandardCharsets.UTF_8)) { + String trimmedLine = line.trim(); + if (trimmedLine.isEmpty() || trimmedLine.startsWith("//")) continue; + + int equalsIndex = trimmedLine.indexOf('='); + if (equalsIndex < 0) continue; + + String key = trimmedLine.substring(0, equalsIndex).trim(); + String value = trimmedLine.substring(equalsIndex + 1).trim(); + int semicolonIndex = value.indexOf(';'); + if (semicolonIndex < 0) continue; + value = value.substring(0, semicolonIndex).trim(); + if (value.startsWith("\"") && value.endsWith("\"") && value.length() >= 2) + value = value.substring(1, value.length() - 1); + + if (key.equals("name")) name = value; + if (key.equals("publishedid")) publishedId = value; + } + + if (publishedId == null || !publishedId.matches("\\d+")) + throw new IOException("Failed to read publishedid from " + metaFile); + + return new DayZWorkshopMod(modDir, name, publishedId); + } +} diff --git a/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/TaskModsUpdater.java b/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/TaskModsUpdater.java index 6d0b078c..143b671a 100644 --- a/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/TaskModsUpdater.java +++ b/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/TaskModsUpdater.java @@ -15,6 +15,7 @@ import com.osiris.autoplug.client.tasks.updater.plugins.ResourceFinder; import com.osiris.autoplug.client.tasks.updater.search.SearchResult; import com.osiris.autoplug.client.utils.GD; +import com.osiris.autoplug.client.utils.SteamCMD; import com.osiris.autoplug.client.utils.UtilsFile; import com.osiris.autoplug.client.utils.UtilsMinecraft; import com.osiris.betterthread.BThread; @@ -22,6 +23,8 @@ import com.osiris.betterthread.BWarning; import com.osiris.dyml.YamlSection; import com.osiris.dyml.exceptions.DuplicateKeyException; +import com.osiris.jlib.logger.AL; +import org.apache.commons.io.FileUtils; import org.jetbrains.annotations.NotNull; import java.io.DataInputStream; @@ -81,7 +84,14 @@ public void runAtStart() throws Exception { setStatus("Fetching latest mod data..."); userProfile = updaterConfig.mods_updater_profile.asString(); - this.allMods.addAll(new UtilsMinecraft().getMods(FileManager.convertRelativeToAbsolutePath(updaterConfig.mods_updater_path.asString()))); + File modsDir = FileManager.convertRelativeToAbsolutePath(updaterConfig.mods_updater_path.asString()); + List dayZWorkshopMods = DayZWorkshopMod.findIn(modsDir); + if (!dayZWorkshopMods.isEmpty()) { + doDayZWorkshopUpdateLogic(dayZWorkshopMods); + return; + } + + this.allMods.addAll(new UtilsMinecraft().getMods(modsDir)); for (MinecraftMod installedMod : allMods) { @@ -337,6 +347,57 @@ else if (code == SearchResult.Type.RESOURCE_NOT_FOUND) } + private void doDayZWorkshopUpdateLogic(@NotNull List dayZWorkshopMods) throws Exception { + setMax(dayZWorkshopMods.size()); + String configuredWorkshopAppId = updaterConfig.mods_updater_dayz_workshop_app_id.asString(); + String workshopAppId = configuredWorkshopAppId == null || configuredWorkshopAppId.isEmpty() ? "221100" : configuredWorkshopAppId; + + if (userProfile.equals(notifyProfile)) { + for (DayZWorkshopMod mod : dayZWorkshopMods) + addInfo("NOTIFY: DayZ mod '" + mod.getName() + "' can be updated with Steam Workshop item " + mod.getPublishedId() + "."); + finish("Found " + dayZWorkshopMods.size() + " DayZ workshop mods."); + return; + } + + SteamCMD steamCMD = new SteamCMD(); + int successfulUpdates = 0; + int checkedMods = 0; + for (DayZWorkshopMod mod : dayZWorkshopMods) { + checkedMods++; + setStatus("Updating DayZ mod '" + mod.getName() + "' (" + checkedMods + "/" + dayZWorkshopMods.size() + ")..."); + boolean isSuccess = steamCMD.installOrUpdateWorkshopItem(workshopAppId, mod.getPublishedId(), line -> { + AL.debug(this.getClass(), "SteamCMD-Out: " + line); + setStatus(line); + }, errLine -> { + AL.debug(this.getClass(), "SteamCMD-Err-Out: " + errLine); + setStatus(errLine); + addWarning(errLine); + }); + if (!isSuccess) { + addWarning("Failed to update DayZ mod '" + mod.getName() + "' via SteamCMD."); + continue; + } + + File downloadedDir = steamCMD.getWorkshopItemDir(workshopAppId, mod.getPublishedId()); + if (userProfile.equals(manualProfile)) { + addInfo("MANUAL: Downloaded DayZ mod '" + mod.getName() + "' to " + downloadedDir.getAbsolutePath() + "."); + } else { + setStatus("Copying DayZ mod '" + mod.getName() + "' into " + mod.getDirectory().getAbsolutePath() + "..."); + FileUtils.copyDirectory(downloadedDir, mod.getDirectory()); + addInfo("Updated DayZ mod '" + mod.getName() + "' from Steam Workshop item " + mod.getPublishedId() + "."); + } + successfulUpdates++; + } + + if (successfulUpdates == dayZWorkshopMods.size()) { + setSuccess(true); + finish("Updated " + successfulUpdates + " DayZ workshop mods."); + } else { + setSuccess(false); + finish("Updated " + successfulUpdates + "/" + dayZWorkshopMods.size() + " DayZ workshop mods."); + } + } + private void doDownloadLogic(@NotNull MinecraftMod mod, SearchResult result) { SearchResult.Type code = result.type; String type = result.getDownloadType(); // The file type to download (Note: When 'external' is returned nothing will be downloaded. Working on a fix for this!) diff --git a/src/main/java/com/osiris/autoplug/client/utils/SteamCMD.java b/src/main/java/com/osiris/autoplug/client/utils/SteamCMD.java index 9c971e52..6b0f8dfa 100644 --- a/src/main/java/com/osiris/autoplug/client/utils/SteamCMD.java +++ b/src/main/java/com/osiris/autoplug/client/utils/SteamCMD.java @@ -35,6 +35,7 @@ @SuppressWarnings({"WeakerAccess", "unused"}) public class SteamCMD { + private static final String STEAMCMD_WORKSHOP_COMMAND = "+login {LOGIN} +workshop_download_item {WORKSHOP_APP} {WORKSHOP_ITEM} validate +quit"; private final String steamcmdArchive = "steamcmd" + (isWindows ? ".zip" : isMac ? "_osx.tar.gz" : "_linux.tar.gz"); private final String steamcmdExtension = isWindows ? ".exe" : ".sh"; private final String steamcmdExecutable = "steamcmd" + steamcmdExtension; @@ -115,8 +116,7 @@ public boolean installOrUpdateServer(String appId, Consumer onLog, Consu AL.debug(this.getClass(), "Installing app " + appId + "..."); onLog.accept("Installing app " + appId + "..."); - String login = new UpdaterConfig().server_steamcmd_login.asString(); - if (login == null || login.isEmpty()) login = "anonymous"; + String login = getLogin(); File gameInstallDir = new File(dirSteamServersDownloads + "/" + appId); gameInstallDir.mkdirs(); @@ -159,10 +159,65 @@ public boolean installOrUpdateServer(String appId, Consumer onLog, Consu } } + public boolean installOrUpdateWorkshopItem(String workshopAppId, String workshopItemId, Consumer onLog, Consumer onLogErr) { + try { + if (!installIfNeeded()) return false; + AL.debug(this.getClass(), "Installing workshop item " + workshopItemId + " for app " + workshopAppId + "..."); + onLog.accept("Installing workshop item " + workshopItemId + "..."); + + String command = buildWorkshopItemCommand(getLogin(), workshopAppId, workshopItemId); + List logLines = new ArrayList<>(); + List logErrLines = new ArrayList<>(); + AtomicBoolean isFinished = new AtomicBoolean(false); + AtomicBoolean isSuccess = new AtomicBoolean(true); + AsyncTerminal terminal = new AsyncTerminal(destDir, line -> { + onLog.accept(line); + logLines.add(line); + String lowerLine = line.toLowerCase(); + if (lowerLine.startsWith("success.") && lowerLine.contains("item " + workshopItemId.toLowerCase())) + isFinished.set(true); + if (lowerLine.startsWith("error!")) { + isSuccess.set(false); + isFinished.set(true); + } + }, line -> { + onLogErr.accept(line); + logErrLines.add(line); + }, destExe.getAbsolutePath() + " " + command); + + Thread thread = new Thread(() -> terminal.process.destroy()); + Runtime.getRuntime().addShutdownHook(thread); + while (!isFinished.get() && terminal.process.isAlive()) Thread.sleep(100); + if (terminal.process.isAlive()) terminal.process.destroy(); + Runtime.getRuntime().removeShutdownHook(thread); + return isSuccess.get() && getWorkshopItemDir(workshopAppId, workshopItemId).exists(); + } catch (Exception e) { + AL.warn(e); + return false; + } + } + + public File getWorkshopItemDir(String workshopAppId, String workshopItemId) { + return new File(destDir + "/steamapps/workshop/content/" + workshopAppId + "/" + workshopItemId); + } + + static String buildWorkshopItemCommand(String login, String workshopAppId, String workshopItemId) { + return STEAMCMD_WORKSHOP_COMMAND + .replace("{LOGIN}", login) + .replace("{WORKSHOP_APP}", workshopAppId) + .replace("{WORKSHOP_ITEM}", workshopItemId); + } + + private String getLogin() throws NotLoadedException, YamlReaderException, YamlWriterException, IOException, IllegalKeyException, DuplicateKeyException, IllegalListException { + String login = new UpdaterConfig().server_steamcmd_login.asString(); + if (login == null || login.isEmpty()) login = "anonymous"; + return login; + } + public String getResolutionForError(String error) { for (Map.Entry entry : errorResolutions.entrySet()) if (error.contains(entry.getKey())) return entry.getValue(); return "Unknown. :("; } -} \ No newline at end of file +} diff --git a/src/test/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopModTest.java b/src/test/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopModTest.java new file mode 100644 index 00000000..75a74627 --- /dev/null +++ b/src/test/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopModTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 Osiris-Team. + * All rights reserved. + * + * This software is copyrighted work, licensed under the terms + * of the MIT-License. Consult the "LICENSE" file for details. + */ + +package com.osiris.autoplug.client.tasks.updater.mods; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class DayZWorkshopModTest { + + @TempDir + Path tempDir; + + @Test + void readsPublishedIdAndNameFromMetaCpp() throws Exception { + File modDir = tempDir.resolve("@CF").toFile(); + modDir.mkdirs(); + File metaFile = writeMeta(modDir, + "protocol = 1;", + "publishedid = 1559212036;", + "name = \"CF\";", + "timestamp = 5249804932187309401;"); + + DayZWorkshopMod mod = DayZWorkshopMod.readFromMeta(modDir, metaFile); + + assertEquals(modDir, mod.getDirectory()); + assertEquals("CF", mod.getName()); + assertEquals("1559212036", mod.getPublishedId()); + } + + @Test + void findsModsWithMetaCppInDirectSubdirectories() throws Exception { + File firstModDir = tempDir.resolve("@CF").toFile(); + File secondModDir = tempDir.resolve("@CommunityOnlineTools").toFile(); + File ignoredDir = tempDir.resolve("keys").toFile(); + firstModDir.mkdirs(); + secondModDir.mkdirs(); + ignoredDir.mkdirs(); + writeMeta(firstModDir, "publishedid = 1559212036;", "name = \"CF\";"); + writeMeta(secondModDir, "publishedid = 1564026768;", "name = \"Community-Online-Tools\";"); + + List mods = DayZWorkshopMod.findIn(tempDir.toFile()); + + assertEquals(2, mods.size()); + assertEquals("1559212036", mods.get(0).getPublishedId()); + assertEquals("1564026768", mods.get(1).getPublishedId()); + } + + private File writeMeta(File modDir, String... lines) throws Exception { + File metaFile = new File(modDir, "meta.cpp"); + Files.write(metaFile.toPath(), Arrays.asList(lines), StandardCharsets.UTF_8); + return metaFile; + } +} diff --git a/src/test/java/com/osiris/autoplug/client/utils/SteamCMDTest.java b/src/test/java/com/osiris/autoplug/client/utils/SteamCMDTest.java index 7d39cf1f..95c1ca44 100644 --- a/src/test/java/com/osiris/autoplug/client/utils/SteamCMDTest.java +++ b/src/test/java/com/osiris/autoplug/client/utils/SteamCMDTest.java @@ -13,11 +13,20 @@ import java.io.IOException; +import static org.junit.jupiter.api.Assertions.assertEquals; + class SteamCMDTest { + @Test + void buildsWorkshopItemCommand() { + String command = SteamCMD.buildWorkshopItemCommand("anonymous", "221100", "1559212036"); + + assertEquals("+login anonymous +workshop_download_item 221100 1559212036 validate +quit", command); + } + @Test void installSteamcmd() throws IOException { UtilsTest.init(); new SteamCMD().installIfNeeded(); } -} \ No newline at end of file +} From b6604e275ae60b8abe508fd7c970576a27158546 Mon Sep 17 00:00:00 2001 From: JustABiologist Date: Thu, 14 May 2026 23:14:56 +0200 Subject: [PATCH 2/3] Generalize Steam Workshop mod updater --- .../client/configs/UpdaterConfig.java | 7 ++- ...WorkshopMod.java => SteamWorkshopMod.java} | 12 +++--- .../tasks/updater/mods/TaskModsUpdater.java | 43 +++++++++++-------- ...ModTest.java => SteamWorkshopModTest.java} | 6 +-- 4 files changed, 36 insertions(+), 32 deletions(-) rename src/main/java/com/osiris/autoplug/client/tasks/updater/mods/{DayZWorkshopMod.java => SteamWorkshopMod.java} (86%) rename src/test/java/com/osiris/autoplug/client/tasks/updater/mods/{DayZWorkshopModTest.java => SteamWorkshopModTest.java} (91%) diff --git a/src/main/java/com/osiris/autoplug/client/configs/UpdaterConfig.java b/src/main/java/com/osiris/autoplug/client/configs/UpdaterConfig.java index 7729e889..6c815a82 100644 --- a/src/main/java/com/osiris/autoplug/client/configs/UpdaterConfig.java +++ b/src/main/java/com/osiris/autoplug/client/configs/UpdaterConfig.java @@ -64,7 +64,6 @@ public class UpdaterConfig extends MyYaml { public YamlSection mods_updater_version; public YamlSection mods_updater_async; public YamlSection mods_update_check_name_for_mod_loader; - public YamlSection mods_updater_dayz_workshop_app_id; public UpdaterConfig() throws IOException, DuplicateKeyException, YamlReaderException, IllegalListException, NotLoadedException, IllegalKeyException, YamlWriterException { @@ -231,9 +230,9 @@ public UpdaterConfig() throws IOException, DuplicateKeyException, YamlReaderExce mods_update_check_name_for_mod_loader = put(name, "mods-updater", "check-name-for-mod-loader").setDefValues("false").setComments( "Only relevant for determining if a curseforge mod release is forge or fabric.", "If enabled additionally checks the mod name to see if it contains fabric or forge."); - mods_updater_dayz_workshop_app_id = put(name, "mods-updater", "dayz-workshop-app-id").setDefValues("221100").setComments( - "Steam workshop app-id used for DayZ mods.", - "When the mods path contains DayZ mod folders with meta.cpp files, AutoPlug reads their publishedid and updates them through SteamCMD."); + mods_updater_path.setComments( + "Path to your mods folder.", + "Steam Workshop mods with supported metadata can be updated through SteamCMD when server-updater.software is set to a numeric Steam app-id."); save(); unlockFile(); diff --git a/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopMod.java b/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopMod.java similarity index 86% rename from src/main/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopMod.java rename to src/main/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopMod.java index 9523742e..559ab459 100644 --- a/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopMod.java +++ b/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopMod.java @@ -20,12 +20,12 @@ import java.util.Comparator; import java.util.List; -public class DayZWorkshopMod { +public class SteamWorkshopMod { private final File directory; private final String name; private final String publishedId; - public DayZWorkshopMod(File directory, String name, String publishedId) { + public SteamWorkshopMod(File directory, String name, String publishedId) { this.directory = directory; this.name = name; this.publishedId = publishedId; @@ -44,9 +44,9 @@ public String getPublishedId() { } @NotNull - public static List findIn(File dir) throws IOException { + public static List findIn(File dir) throws IOException { if (!dir.exists()) throw new FileNotFoundException("Directory does not exist: " + dir); - List mods = new ArrayList<>(); + List mods = new ArrayList<>(); File[] files = dir.listFiles(); if (files == null) return mods; Arrays.sort(files, Comparator.comparing(File::getName)); @@ -59,7 +59,7 @@ public static List findIn(File dir) throws IOException { return mods; } - static DayZWorkshopMod readFromMeta(File modDir, File metaFile) throws IOException { + static SteamWorkshopMod readFromMeta(File modDir, File metaFile) throws IOException { String name = modDir.getName(); String publishedId = null; for (String line : Files.readAllLines(metaFile.toPath(), StandardCharsets.UTF_8)) { @@ -84,6 +84,6 @@ static DayZWorkshopMod readFromMeta(File modDir, File metaFile) throws IOExcepti if (publishedId == null || !publishedId.matches("\\d+")) throw new IOException("Failed to read publishedid from " + metaFile); - return new DayZWorkshopMod(modDir, name, publishedId); + return new SteamWorkshopMod(modDir, name, publishedId); } } diff --git a/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/TaskModsUpdater.java b/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/TaskModsUpdater.java index 143b671a..01a97312 100644 --- a/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/TaskModsUpdater.java +++ b/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/TaskModsUpdater.java @@ -85,9 +85,9 @@ public void runAtStart() throws Exception { userProfile = updaterConfig.mods_updater_profile.asString(); File modsDir = FileManager.convertRelativeToAbsolutePath(updaterConfig.mods_updater_path.asString()); - List dayZWorkshopMods = DayZWorkshopMod.findIn(modsDir); - if (!dayZWorkshopMods.isEmpty()) { - doDayZWorkshopUpdateLogic(dayZWorkshopMods); + List steamWorkshopMods = SteamWorkshopMod.findIn(modsDir); + if (!steamWorkshopMods.isEmpty()) { + doSteamWorkshopUpdateLogic(steamWorkshopMods); return; } @@ -347,24 +347,29 @@ else if (code == SearchResult.Type.RESOURCE_NOT_FOUND) } - private void doDayZWorkshopUpdateLogic(@NotNull List dayZWorkshopMods) throws Exception { - setMax(dayZWorkshopMods.size()); - String configuredWorkshopAppId = updaterConfig.mods_updater_dayz_workshop_app_id.asString(); - String workshopAppId = configuredWorkshopAppId == null || configuredWorkshopAppId.isEmpty() ? "221100" : configuredWorkshopAppId; + private void doSteamWorkshopUpdateLogic(@NotNull List steamWorkshopMods) throws Exception { + setMax(steamWorkshopMods.size()); + String workshopAppId = updaterConfig.server_software.asString(); + if (workshopAppId == null || !workshopAppId.matches("\\d+")) { + setSuccess(false); + addWarning("Steam Workshop mods were found, but server-updater.software is not a numeric Steam app-id."); + finish("Found " + steamWorkshopMods.size() + " Steam Workshop mods, but no Steam app-id is configured."); + return; + } if (userProfile.equals(notifyProfile)) { - for (DayZWorkshopMod mod : dayZWorkshopMods) - addInfo("NOTIFY: DayZ mod '" + mod.getName() + "' can be updated with Steam Workshop item " + mod.getPublishedId() + "."); - finish("Found " + dayZWorkshopMods.size() + " DayZ workshop mods."); + for (SteamWorkshopMod mod : steamWorkshopMods) + addInfo("NOTIFY: Steam Workshop mod '" + mod.getName() + "' can be updated with Workshop item " + mod.getPublishedId() + " for app " + workshopAppId + "."); + finish("Found " + steamWorkshopMods.size() + " Steam Workshop mods."); return; } SteamCMD steamCMD = new SteamCMD(); int successfulUpdates = 0; int checkedMods = 0; - for (DayZWorkshopMod mod : dayZWorkshopMods) { + for (SteamWorkshopMod mod : steamWorkshopMods) { checkedMods++; - setStatus("Updating DayZ mod '" + mod.getName() + "' (" + checkedMods + "/" + dayZWorkshopMods.size() + ")..."); + setStatus("Updating Steam Workshop mod '" + mod.getName() + "' (" + checkedMods + "/" + steamWorkshopMods.size() + ")..."); boolean isSuccess = steamCMD.installOrUpdateWorkshopItem(workshopAppId, mod.getPublishedId(), line -> { AL.debug(this.getClass(), "SteamCMD-Out: " + line); setStatus(line); @@ -374,27 +379,27 @@ private void doDayZWorkshopUpdateLogic(@NotNull List dayZWorksh addWarning(errLine); }); if (!isSuccess) { - addWarning("Failed to update DayZ mod '" + mod.getName() + "' via SteamCMD."); + addWarning("Failed to update Steam Workshop mod '" + mod.getName() + "' via SteamCMD."); continue; } File downloadedDir = steamCMD.getWorkshopItemDir(workshopAppId, mod.getPublishedId()); if (userProfile.equals(manualProfile)) { - addInfo("MANUAL: Downloaded DayZ mod '" + mod.getName() + "' to " + downloadedDir.getAbsolutePath() + "."); + addInfo("MANUAL: Downloaded Steam Workshop mod '" + mod.getName() + "' to " + downloadedDir.getAbsolutePath() + "."); } else { - setStatus("Copying DayZ mod '" + mod.getName() + "' into " + mod.getDirectory().getAbsolutePath() + "..."); + setStatus("Copying Steam Workshop mod '" + mod.getName() + "' into " + mod.getDirectory().getAbsolutePath() + "..."); FileUtils.copyDirectory(downloadedDir, mod.getDirectory()); - addInfo("Updated DayZ mod '" + mod.getName() + "' from Steam Workshop item " + mod.getPublishedId() + "."); + addInfo("Updated Steam Workshop mod '" + mod.getName() + "' from Workshop item " + mod.getPublishedId() + "."); } successfulUpdates++; } - if (successfulUpdates == dayZWorkshopMods.size()) { + if (successfulUpdates == steamWorkshopMods.size()) { setSuccess(true); - finish("Updated " + successfulUpdates + " DayZ workshop mods."); + finish("Updated " + successfulUpdates + " Steam Workshop mods."); } else { setSuccess(false); - finish("Updated " + successfulUpdates + "/" + dayZWorkshopMods.size() + " DayZ workshop mods."); + finish("Updated " + successfulUpdates + "/" + steamWorkshopMods.size() + " Steam Workshop mods."); } } diff --git a/src/test/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopModTest.java b/src/test/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopModTest.java similarity index 91% rename from src/test/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopModTest.java rename to src/test/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopModTest.java index 75a74627..77a56c02 100644 --- a/src/test/java/com/osiris/autoplug/client/tasks/updater/mods/DayZWorkshopModTest.java +++ b/src/test/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopModTest.java @@ -20,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -class DayZWorkshopModTest { +class SteamWorkshopModTest { @TempDir Path tempDir; @@ -35,7 +35,7 @@ void readsPublishedIdAndNameFromMetaCpp() throws Exception { "name = \"CF\";", "timestamp = 5249804932187309401;"); - DayZWorkshopMod mod = DayZWorkshopMod.readFromMeta(modDir, metaFile); + SteamWorkshopMod mod = SteamWorkshopMod.readFromMeta(modDir, metaFile); assertEquals(modDir, mod.getDirectory()); assertEquals("CF", mod.getName()); @@ -53,7 +53,7 @@ void findsModsWithMetaCppInDirectSubdirectories() throws Exception { writeMeta(firstModDir, "publishedid = 1559212036;", "name = \"CF\";"); writeMeta(secondModDir, "publishedid = 1564026768;", "name = \"Community-Online-Tools\";"); - List mods = DayZWorkshopMod.findIn(tempDir.toFile()); + List mods = SteamWorkshopMod.findIn(tempDir.toFile()); assertEquals(2, mods.size()); assertEquals("1559212036", mods.get(0).getPublishedId()); From 39f93ce1e7f9a9a13e0e0212c75209fe3acb55e4 Mon Sep 17 00:00:00 2001 From: JustABiologist Date: Fri, 15 May 2026 16:50:18 +0200 Subject: [PATCH 3/3] Integrate Steam Workshop mods into updater flow --- .../autoplug/client/configs/ModsConfig.java | 1 + .../tasks/updater/mods/SteamWorkshopMod.java | 16 +-- .../tasks/updater/mods/TaskModsUpdater.java | 111 ++++++++++-------- .../updater/mods/SteamWorkshopModTest.java | 95 +++++++++++++++ 4 files changed, 166 insertions(+), 57 deletions(-) diff --git a/src/main/java/com/osiris/autoplug/client/configs/ModsConfig.java b/src/main/java/com/osiris/autoplug/client/configs/ModsConfig.java index 6a9870ab..91cd45af 100644 --- a/src/main/java/com/osiris/autoplug/client/configs/ModsConfig.java +++ b/src/main/java/com/osiris/autoplug/client/configs/ModsConfig.java @@ -44,6 +44,7 @@ public ModsConfig() throws IOException, DuplicateKeyException, YamlReaderExcepti "If there are mods that weren't found by the search-algorithm, you can add an id (spigot or bukkit) and a custom link (optional & must be a static link to the latest mod jar).\n" + "modrinth-id: Is the 'Project-ID' and can be found on the mods modrinth site inside of the 'About' box, under 'Technical Information' at the bottom left.\n" + "curseforge-id: Is also called 'Project-ID' and can be found on the mods curseforge site inside of the 'About' box at the right.\n" + + "steam-workshop-id: The Steam Workshop item id read from the mods metadata. Requires server-updater.software to be a numeric Steam app-id.\n" + "ignore-content-type: If true, does not check if the downloaded file is of type jar or zip, and downloads it anyway.\n" + "force-latest: If true, does not search for updates compatible with this Minecraft version and simply picks the latest release.\n" + "force-update: If true, downloads the update every time even if its already on the latest version.\n" + diff --git a/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopMod.java b/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopMod.java index 559ab459..5510b49f 100644 --- a/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopMod.java +++ b/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopMod.java @@ -20,14 +20,13 @@ import java.util.Comparator; import java.util.List; -public class SteamWorkshopMod { +public class SteamWorkshopMod extends MinecraftMod { private final File directory; - private final String name; - private final String publishedId; + private String publishedId; public SteamWorkshopMod(File directory, String name, String publishedId) { + super(directory.getAbsolutePath(), name, publishedId, "Steam Workshop", null, null, null); this.directory = directory; - this.name = name; this.publishedId = publishedId; } @@ -35,14 +34,15 @@ public File getDirectory() { return directory; } - public String getName() { - return name; - } - public String getPublishedId() { return publishedId; } + public void setPublishedId(String publishedId) { + this.publishedId = publishedId; + setVersion(publishedId); + } + @NotNull public static List findIn(File dir) throws IOException { if (!dir.exists()) throw new FileNotFoundException("Directory does not exist: " + dir); diff --git a/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/TaskModsUpdater.java b/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/TaskModsUpdater.java index 01a97312..5ec1078f 100644 --- a/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/TaskModsUpdater.java +++ b/src/main/java/com/osiris/autoplug/client/tasks/updater/mods/TaskModsUpdater.java @@ -85,13 +85,8 @@ public void runAtStart() throws Exception { userProfile = updaterConfig.mods_updater_profile.asString(); File modsDir = FileManager.convertRelativeToAbsolutePath(updaterConfig.mods_updater_path.asString()); - List steamWorkshopMods = SteamWorkshopMod.findIn(modsDir); - if (!steamWorkshopMods.isEmpty()) { - doSteamWorkshopUpdateLogic(steamWorkshopMods); - return; - } - this.allMods.addAll(new UtilsMinecraft().getMods(modsDir)); + this.allMods.addAll(SteamWorkshopMod.findIn(modsDir)); for (MinecraftMod installedMod : allMods) { @@ -106,6 +101,7 @@ public void runAtStart() throws Exception { YamlSection author = modsConfig.put(name, plName, "author").setDefValues(installedMod.getAuthor()); YamlSection modrinthId = modsConfig.put(name, plName, "modrinth-id"); YamlSection curseforgeId = modsConfig.put(name, plName, "curseforge-id"); + YamlSection steamWorkshopId = modsConfig.put(name, plName, "steam-workshop-id"); YamlSection ignoreContentType = modsConfig.put(name, plName, "ignore-content-type").setDefValues("false"); YamlSection forceLatest = modsConfig.put(name, plName, "force-latest").setDefValues("false"); YamlSection forceUpdate = modsConfig.put(name, plName, "force-update").setDefValues("false"); @@ -121,6 +117,8 @@ public void runAtStart() throws Exception { modrinthId.setValues(installedMod.modrinthId); if (installedMod.curseforgeId != null && curseforgeId.asString() == null) curseforgeId.setValues(installedMod.curseforgeId); + if (installedMod instanceof SteamWorkshopMod) + steamWorkshopId.setValues(((SteamWorkshopMod) installedMod).getPublishedId()); // Update the detailed mods in-memory values installedMod.modrinthId = (modrinthId.asString()); @@ -135,6 +133,14 @@ public void runAtStart() throws Exception { installedMod.jenkinsArtifactName = (jenkinsArtifactName.asString()); installedMod.jenkinsBuildId = (jenkinsBuildId.asInt()); installedMod.forceUpdate = forceUpdate.asBoolean(); + if (installedMod instanceof SteamWorkshopMod) { + ((SteamWorkshopMod) installedMod).setPublishedId(steamWorkshopId.asString()); + if (exclude.asBoolean()) + excludedMods.add(installedMod); + else + includedMods.add(installedMod); + continue; + } // Check for missing author in internal config if ((installedMod.getVersion() == null) @@ -194,11 +200,7 @@ public void runAtStart() throws Exception { int sizeBukkitMods = 0; int sizeUnknownMods = 0; int sizeCustomMods = 0; - - - String mcVersion = updaterConfig.mods_updater_version.asString(); - if (mcVersion == null) updaterConfig.server_updater_version.asString(); - if (mcVersion == null) mcVersion = Server.getMCVersion(); + int sizeSteamWorkshopMods = 0; ExecutorService executorService; if (updaterConfig.mods_updater_async.asBoolean()) @@ -207,11 +209,15 @@ public void runAtStart() throws Exception { executorService = Executors.newSingleThreadExecutor(); InstalledModLoader modLoader = new InstalledModLoader(); List> activeFutures = new ArrayList<>(); + String mcVersion = null; for (MinecraftMod mod : includedMods) { try { setStatus("Initialising update check for " + mod.getName() + "..."); - if (mod.customCheckURL != null) { // Custom Check + if (mod instanceof SteamWorkshopMod) { + sizeSteamWorkshopMods++; + activeFutures.add(executorService.submit(() -> doSteamWorkshopUpdateLogic((SteamWorkshopMod) mod))); + } else if (mod.customCheckURL != null) { // Custom Check sizeCustomMods++; activeFutures.add(executorService.submit(() -> new ResourceFinder().findByCustomCheckURL(mod))); } else if (mod.jenkinsProjectUrl != null) { // JENKINS MOD @@ -223,6 +229,11 @@ public void runAtStart() throws Exception { } else { sizeUnknownMods++; // MODRINTH OR CURSEFORGE MOD mod.ignoreContentType = true; // TODO temporary workaround for xamazon-json content type curseforge/bukkit issue: https://github.com/Osiris-Team/AutoPlug-Client/issues/109 + if (mcVersion == null) { + mcVersion = updaterConfig.mods_updater_version.asString(); + if (mcVersion == null) mcVersion = updaterConfig.server_updater_version.asString(); + if (mcVersion == null) mcVersion = Server.getMCVersion(); + } String finalMcVersion = mcVersion; activeFutures.add(executorService.submit(() -> new ResourceFinder().findByModrinthOrCurseforge(modLoader, mod, finalMcVersion, updaterConfig.mods_update_check_name_for_mod_loader.asBoolean()))); } @@ -255,7 +266,13 @@ public void runAtStart() throws Exception { String resultModrinthId = mod.modrinthId; String resultCurseForgeId = mod.curseforgeId; this.setStatus("Checked '" + mod.getName() + "' mod (" + results.size() + "/" + includedSize + ")"); - if (code == SearchResult.Type.UP_TO_DATE || code == SearchResult.Type.UPDATE_AVAILABLE) { + if (mod instanceof SteamWorkshopMod) { + if (code == SearchResult.Type.API_ERROR) + if (result.getException() != null) + getWarnings().add(new BWarning(this, result.getException(), "There was a Steam Workshop update error for " + mod.getName() + "!")); + else + getWarnings().add(new BWarning(this, new Exception("There was a Steam Workshop update error for " + mod.getName() + "!"))); + } else if (code == SearchResult.Type.UP_TO_DATE || code == SearchResult.Type.UPDATE_AVAILABLE) { doDownloadLogic(mod, result); } else if (code == SearchResult.Type.API_ERROR) if (result.getException() != null) @@ -347,60 +364,56 @@ else if (code == SearchResult.Type.RESOURCE_NOT_FOUND) } - private void doSteamWorkshopUpdateLogic(@NotNull List steamWorkshopMods) throws Exception { - setMax(steamWorkshopMods.size()); + private SearchResult doSteamWorkshopUpdateLogic(@NotNull SteamWorkshopMod mod) { + SearchResult result = new SearchResult(null, SearchResult.Type.UP_TO_DATE, mod.getPublishedId(), null, "steam-workshop", null, null, false); + result.mod = mod; String workshopAppId = updaterConfig.server_software.asString(); if (workshopAppId == null || !workshopAppId.matches("\\d+")) { - setSuccess(false); - addWarning("Steam Workshop mods were found, but server-updater.software is not a numeric Steam app-id."); - finish("Found " + steamWorkshopMods.size() + " Steam Workshop mods, but no Steam app-id is configured."); - return; + result.type = SearchResult.Type.API_ERROR; + result.setException(new Exception("Steam Workshop mod '" + mod.getName() + "' was found, but server-updater.software is not a numeric Steam app-id.")); + return result; } if (userProfile.equals(notifyProfile)) { - for (SteamWorkshopMod mod : steamWorkshopMods) - addInfo("NOTIFY: Steam Workshop mod '" + mod.getName() + "' can be updated with Workshop item " + mod.getPublishedId() + " for app " + workshopAppId + "."); - finish("Found " + steamWorkshopMods.size() + " Steam Workshop mods."); - return; + addInfo("NOTIFY: Steam Workshop mod '" + mod.getName() + "' can be updated with Workshop item " + mod.getPublishedId() + " for app " + workshopAppId + "."); + result.type = SearchResult.Type.UPDATE_AVAILABLE; + return result; } - SteamCMD steamCMD = new SteamCMD(); - int successfulUpdates = 0; - int checkedMods = 0; - for (SteamWorkshopMod mod : steamWorkshopMods) { - checkedMods++; - setStatus("Updating Steam Workshop mod '" + mod.getName() + "' (" + checkedMods + "/" + steamWorkshopMods.size() + ")..."); - boolean isSuccess = steamCMD.installOrUpdateWorkshopItem(workshopAppId, mod.getPublishedId(), line -> { - AL.debug(this.getClass(), "SteamCMD-Out: " + line); - setStatus(line); - }, errLine -> { - AL.debug(this.getClass(), "SteamCMD-Err-Out: " + errLine); - setStatus(errLine); - addWarning(errLine); - }); - if (!isSuccess) { - addWarning("Failed to update Steam Workshop mod '" + mod.getName() + "' via SteamCMD."); - continue; - } + try { + SteamCMD steamCMD = createSteamCMD(); + setStatus("Updating Steam Workshop mod '" + mod.getName() + "'..."); + boolean isSuccess = steamCMD.installOrUpdateWorkshopItem(workshopAppId, mod.getPublishedId(), + line -> { + AL.debug(this.getClass(), "SteamCMD-Out: " + line); + setStatus(line); + }, errLine -> { + AL.debug(this.getClass(), "SteamCMD-Err-Out: " + errLine); + setStatus(errLine); + addWarning(errLine); + }); + if (!isSuccess) + throw new Exception("Failed to update Steam Workshop mod '" + mod.getName() + "' via SteamCMD."); File downloadedDir = steamCMD.getWorkshopItemDir(workshopAppId, mod.getPublishedId()); if (userProfile.equals(manualProfile)) { addInfo("MANUAL: Downloaded Steam Workshop mod '" + mod.getName() + "' to " + downloadedDir.getAbsolutePath() + "."); + result.type = SearchResult.Type.UPDATE_DOWNLOADED; } else { setStatus("Copying Steam Workshop mod '" + mod.getName() + "' into " + mod.getDirectory().getAbsolutePath() + "..."); FileUtils.copyDirectory(downloadedDir, mod.getDirectory()); addInfo("Updated Steam Workshop mod '" + mod.getName() + "' from Workshop item " + mod.getPublishedId() + "."); + result.type = SearchResult.Type.UPDATE_INSTALLED; } - successfulUpdates++; + } catch (Exception e) { + result.type = SearchResult.Type.API_ERROR; + result.setException(e); } + return result; + } - if (successfulUpdates == steamWorkshopMods.size()) { - setSuccess(true); - finish("Updated " + successfulUpdates + " Steam Workshop mods."); - } else { - setSuccess(false); - finish("Updated " + successfulUpdates + "/" + steamWorkshopMods.size() + " Steam Workshop mods."); - } + SteamCMD createSteamCMD() { + return new SteamCMD(); } private void doDownloadLogic(@NotNull MinecraftMod mod, SearchResult result) { diff --git a/src/test/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopModTest.java b/src/test/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopModTest.java index 77a56c02..6a651220 100644 --- a/src/test/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopModTest.java +++ b/src/test/java/com/osiris/autoplug/client/tasks/updater/mods/SteamWorkshopModTest.java @@ -8,17 +8,26 @@ package com.osiris.autoplug.client.tasks.updater.mods; +import com.osiris.autoplug.client.configs.ModsConfig; +import com.osiris.autoplug.client.configs.UpdaterConfig; +import com.osiris.autoplug.client.utils.GD; +import com.osiris.autoplug.client.utils.SteamCMD; +import com.osiris.betterthread.BThreadManager; +import com.osiris.jlib.logger.AL; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import java.io.File; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.List; +import java.util.function.Consumer; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; class SteamWorkshopModTest { @@ -60,9 +69,95 @@ void findsModsWithMetaCppInDirectSubdirectories() throws Exception { assertEquals("1564026768", mods.get(1).getPublishedId()); } + @Test + void taskUpdatesWorkshopModThroughSteamCmdAndCachesMetadata() throws Exception { + File oldWorkingDir = GD.WORKING_DIR; + File oldDownloadsDir = GD.DOWNLOADS_DIR; + String oldUserDir = System.getProperty("user.dir"); + useWorkingDir(tempDir); + try { + File modDir = tempDir.resolve("mods/@CF").toFile(); + modDir.mkdirs(); + writeMeta(modDir, "publishedid = 1559212036;", "name = \"CF\";"); + Files.write(modDir.toPath().resolve("old.txt"), Arrays.asList("old"), StandardCharsets.UTF_8); + + File downloadedDir = tempDir.resolve("steamcmd/steamapps/workshop/content/221100/1559212036").toFile(); + downloadedDir.mkdirs(); + Files.write(downloadedDir.toPath().resolve("updated.txt"), Arrays.asList("updated"), StandardCharsets.UTF_8); + + UpdaterConfig updaterConfig = new UpdaterConfig(); + updaterConfig.mods_updater.setValues("true"); + updaterConfig.mods_updater_profile.setValues("AUTOMATIC"); + updaterConfig.mods_updater_path.setValues("./mods"); + updaterConfig.mods_updater_async.setValues("false"); + updaterConfig.server_software.setValues("221100"); + updaterConfig.save(); + + FakeSteamCMD steamCMD = new FakeSteamCMD(downloadedDir); + TaskModsUpdater task = new TaskModsUpdater("ModsUpdater", new BThreadManager()) { + @Override + SteamCMD createSteamCMD() { + return steamCMD; + } + }; + + task.runAtStart(); + + assertEquals(1, steamCMD.updateCalls); + assertEquals("221100", steamCMD.workshopAppId); + assertEquals("1559212036", steamCMD.workshopItemId); + assertEquals("updated", Files.readAllLines(modDir.toPath().resolve("updated.txt"), StandardCharsets.UTF_8).get(0)); + assertTrue(task.getWarnings().isEmpty()); + + ModsConfig modsConfig = new ModsConfig(); + modsConfig.load(); + assertEquals("1559212036", modsConfig.get("mods", "CF", "steam-workshop-id").asString()); + } finally { + System.setProperty("user.dir", oldUserDir); + GD.WORKING_DIR = oldWorkingDir; + GD.DOWNLOADS_DIR = oldDownloadsDir; + } + } + private File writeMeta(File modDir, String... lines) throws Exception { File metaFile = new File(modDir, "meta.cpp"); Files.write(metaFile.toPath(), Arrays.asList(lines), StandardCharsets.UTF_8); return metaFile; } + + private void useWorkingDir(Path dir) throws IOException { + System.setProperty("user.dir", dir.toAbsolutePath().toString()); + GD.VERSION = "AutoPlug-Client Test-Version"; + GD.WORKING_DIR = dir.toFile(); + GD.DOWNLOADS_DIR = dir.resolve("autoplug/downloads").toFile(); + GD.DOWNLOADS_DIR.mkdirs(); + File logFile = dir.resolve("autoplug/logs/latest.log").toFile(); + logFile.getParentFile().mkdirs(); + new AL().start("AL", true, logFile, false, false); + } + + private static class FakeSteamCMD extends SteamCMD { + final File workshopItemDir; + int updateCalls; + String workshopAppId; + String workshopItemId; + + FakeSteamCMD(File workshopItemDir) { + this.workshopItemDir = workshopItemDir; + } + + @Override + public boolean installOrUpdateWorkshopItem(String workshopAppId, String workshopItemId, Consumer onLog, Consumer onLogErr) { + this.updateCalls++; + this.workshopAppId = workshopAppId; + this.workshopItemId = workshopItemId; + onLog.accept("Success. Downloaded item " + workshopItemId); + return true; + } + + @Override + public File getWorkshopItemDir(String workshopAppId, String workshopItemId) { + return workshopItemDir; + } + } }