From 57d62d06944a7f102b777e11b6e979a77aab9dd2 Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 1 Oct 2026 00:53:16 +0200 Subject: [PATCH 1/4] Fix v3.0 W3I format regression --- .../languageserver/ProjectConfigBuilder.java | 18 +++++++++----- .../ProjectConfigBuilderTests.java | 2 +- .../tests/MapRequestPatchTargetTests.java | 24 +++++++++++++++++++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java index c3ecc68cb..8e39813d2 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java @@ -8,6 +8,7 @@ import de.peeeq.wurstio.mpq.MpqEditor; import de.peeeq.wurstio.mpq.MpqEditorFactory; import de.peeeq.wurstio.utils.W3InstallationData; +import de.peeeq.wurstscript.CompileTimeInfo; import de.peeeq.wurstscript.RunArgs; import de.peeeq.wurstscript.WLogger; import net.moonlightflower.wc3libs.bin.app.MapFlag; @@ -201,6 +202,9 @@ private static String calculateProjectConfigHash(WurstProjectConfigData projectC WurstBuildConfig buildConfig = buildConfigFromBuildDir(buildDir); sb.append("scriptMode:").append(buildConfig.scriptMode()).append("\n"); sb.append("wc3Patch:").append(buildConfig.wc3PatchName()).append("\n"); + // Reapply metadata after compiler upgrades; W3I serialization rules can change + // without any project configuration changing. + sb.append("compilerVersion:").append(CompileTimeInfo.version).append("\n"); return ImportFile.calculateHash(sb.toString().getBytes(StandardCharsets.UTF_8)); } catch (Exception e) { @@ -241,19 +245,21 @@ private static GameVersion effectiveConfigInjectionVersion(File buildDir, W3Inst .orElseGet(buildConfig::fallbackGameVersion); } - static void applyW3IVersion(WurstBuildConfig buildConfig, W3I w3I, boolean lua) { + public static void applyW3IVersion(WurstBuildConfig buildConfig, W3I w3I, boolean lua) { if (lua) { WLogger.info("Applying lua w3i config"); w3I.setScriptLang(W3I.ScriptLang.LUA); } - // Keep the version from the source map unless the project pins a target patch. - // In that case, emit the newest W3I format supported by that patch. + // Keep the source format when applying a target patch. A target patch can cap the + // format, but upgrading metadata alone leaves its game-version fields inconsistent. buildConfig.configuredGameVersion().ifPresent(version -> { int maxVersion = maxW3IVersionFor(version); + int sourceVersion = w3I.getFileVersion(); + int targetVersion = Math.min(sourceVersion, maxVersion); w3I.setFileVersion(lua - ? Math.max(maxVersion, W3I.EncodingFormat.W3I_0x1F.getVersion()) - : maxVersion); + ? Math.max(targetVersion, W3I.EncodingFormat.W3I_0x1F.getVersion()) + : targetVersion); }); // Lua map metadata needs the script-language field, which is absent in older formats. @@ -275,7 +281,7 @@ private static int maxW3IVersionFor(GameVersion version) { if (version.compareTo(new GameVersion("3.0")) < 0) { return W3I.EncodingFormat.W3I_0x21.getVersion(); } - return W3I.EncodingFormat.W3I_0x27.getVersion(); + return W3I.EncodingFormat.W3I_0x21.getVersion(); } private static WurstBuildConfig buildConfigFromBuildDir(File buildDir) { diff --git a/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilderTests.java b/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilderTests.java index 3e0ca374f..5d3a9f53f 100644 --- a/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilderTests.java +++ b/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilderTests.java @@ -16,7 +16,7 @@ public void pinnedPatchSelectsNewestSupportedW3iFormat() throws Exception { assertW3IVersion("1.31", W3I.EncodingFormat.W3I_0x1C.getVersion()); assertW3IVersion("1.32", W3I.EncodingFormat.W3I_0x1F.getVersion()); assertW3IVersion("2.0", W3I.EncodingFormat.W3I_0x21.getVersion()); - assertW3IVersion("3.0", W3I.EncodingFormat.W3I_0x27.getVersion()); + assertW3IVersion("3.0", W3I.EncodingFormat.W3I_0x21.getVersion()); } @Test diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/MapRequestPatchTargetTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/MapRequestPatchTargetTests.java index 02804490d..52a965d79 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/MapRequestPatchTargetTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/MapRequestPatchTargetTests.java @@ -1,12 +1,14 @@ package tests.wurstscript.tests; import de.peeeq.wurstio.languageserver.ModelManager; +import de.peeeq.wurstio.languageserver.ProjectConfigBuilder; import de.peeeq.wurstio.languageserver.WFile; import de.peeeq.wurstio.languageserver.WurstBuildConfig; import de.peeeq.wurstio.languageserver.requests.MapRequest; import de.peeeq.wurstio.languageserver.requests.RunMap; import de.peeeq.wurstscript.RunArgs; import de.peeeq.wurstio.utils.W3InstallationData; +import net.moonlightflower.wc3libs.bin.app.W3I; import net.moonlightflower.wc3libs.port.GameVersion; import org.testng.annotations.Test; @@ -41,6 +43,28 @@ public void pinnedBuildUsesWurstBuildPatchWithoutWarcraftExeDiscovery() throws E assertFalse(request.gameExe().isPresent(), "Pinned build requests should not need a Warcraft executable"); } + @Test + public void pinnedV3BuildKeepsFormat33SourceForJassAndLua() throws Exception { + Path project = projectWithPatch("v3.0"); + WurstBuildConfig config = WurstBuildConfig.fromWorkspaceRoot(WFile.create(project.toFile())); + int format33 = W3I.EncodingFormat.W3I_0x21.getVersion(); + + for (boolean lua : List.of(false, true)) { + W3I source = new W3I(); + source.setFileVersion(format33); + Path sourceMap = Files.createTempDirectory("w3i-v3-source"); + source.write(sourceMap.resolve(W3I.GAME_PATH.getName()).toFile()); + + W3I built = new W3I(Files.readAllBytes(sourceMap.resolve(W3I.GAME_PATH.getName()))); + ProjectConfigBuilder.applyW3IVersion(config, built, lua); + Path output = Files.createTempFile("w3i-v3-built", ".w3i"); + built.write(output.toFile()); + + assertEquals(new W3I(Files.readAllBytes(output)).getFileVersion(), format33, + "v3.0 must preserve a format-33 source W3I (lua=" + lua + ")"); + } + } + @Test public void pinnedExplicitGameExeUsesPatchVersionWithoutParsingExecutable() throws Exception { Path project = projectWithPatch("TFT-v1.31.1.12173"); From cce8e6bbc21181fdc140832d8f025b942d7f9268 Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 1 Oct 2026 11:10:28 +0200 Subject: [PATCH 2/4] Preserve W3I source formats for map builds --- de.peeeq.wurstscript/build.gradle | 2 +- .../languageserver/ProjectConfigBuilder.java | 34 +++---------------- .../ProjectConfigBuilderTests.java | 12 +++---- 3 files changed, 12 insertions(+), 36 deletions(-) diff --git a/de.peeeq.wurstscript/build.gradle b/de.peeeq.wurstscript/build.gradle index 5dd13708e..6360b413c 100644 --- a/de.peeeq.wurstscript/build.gradle +++ b/de.peeeq.wurstscript/build.gradle @@ -156,7 +156,7 @@ dependencies { implementation 'com.github.albfernandez:juniversalchardet:2.4.0' implementation 'org.xerial:sqlite-jdbc:3.46.1.3' implementation 'com.github.inwc3:JMPQ3:v2.0.1' - implementation 'com.github.inwc3:wc3libs:9a11e1f089' + implementation 'com.github.inwc3:wc3libs:86eb1a1b966' implementation 'com.github.wurstscript:wurst-project-config:348fcd4ef5' implementation 'org.slf4j:slf4j-api:2.0.17' implementation 'ch.qos.logback:logback-classic:1.5.20' diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java index 8e39813d2..be35bbb17 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java @@ -116,7 +116,7 @@ public static MapRequest.CompilationResult apply(WurstProjectConfigData projectC result.w3i = new File(buildDir, "war3map.w3i"); applyW3IVersion(WurstBuildConfig.fromProject(projectConfig, null), w3I, runArgs.isLua()); - w3I.write(result.w3i); + w3I.write(result.w3i, W3I.EncodingFormat.AS_DEFINED); // Apply map header (this is cheap, so we always do it) applyMapHeader(projectConfig, targetMap, w3I.getPlayers().size(), w3I.getMapName(), w3I.getFlags().toInt()); @@ -251,39 +251,15 @@ public static void applyW3IVersion(WurstBuildConfig buildConfig, W3I w3I, boolea w3I.setScriptLang(W3I.ScriptLang.LUA); } - // Keep the source format when applying a target patch. A target patch can cap the - // format, but upgrading metadata alone leaves its game-version fields inconsistent. - buildConfig.configuredGameVersion().ifPresent(version -> { - int maxVersion = maxW3IVersionFor(version); - int sourceVersion = w3I.getFileVersion(); - int targetVersion = Math.min(sourceVersion, maxVersion); - w3I.setFileVersion(lua - ? Math.max(targetVersion, W3I.EncodingFormat.W3I_0x1F.getVersion()) - : targetVersion); - }); - - // Lua map metadata needs the script-language field, which is absent in older formats. + // wc3Patch selects the target game version; it does not select the W3I encoding. + // Keep the source encoding so binary fields read from the map remain available. + // When build config adds fields that require a newer W3I format, promote the format + // at the point where those fields are applied. if (lua && w3I.getFileVersion() < W3I.EncodingFormat.W3I_0x1F.getVersion()) { w3I.setFileVersion(W3I.EncodingFormat.W3I_0x1F.getVersion()); } } - private static int maxW3IVersionFor(GameVersion version) { - if (version.compareTo(new GameVersion("1.31")) < 0) { - return W3I.EncodingFormat.W3I_0x19.getVersion(); - } - if (version.compareTo(new GameVersion("1.32")) < 0) { - return W3I.EncodingFormat.W3I_0x1C.getVersion(); - } - if (version.compareTo(new GameVersion("2.0")) < 0) { - return W3I.EncodingFormat.W3I_0x1F.getVersion(); - } - if (version.compareTo(new GameVersion("3.0")) < 0) { - return W3I.EncodingFormat.W3I_0x21.getVersion(); - } - return W3I.EncodingFormat.W3I_0x21.getVersion(); - } - private static WurstBuildConfig buildConfigFromBuildDir(File buildDir) { java.nio.file.Path projectRoot = buildDir.toPath().getParent(); if (projectRoot == null) { diff --git a/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilderTests.java b/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilderTests.java index 5d3a9f53f..47e44ca4d 100644 --- a/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilderTests.java +++ b/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilderTests.java @@ -11,12 +11,12 @@ public class ProjectConfigBuilderTests { @Test - public void pinnedPatchSelectsNewestSupportedW3iFormat() throws Exception { - assertW3IVersion("1.30", W3I.EncodingFormat.W3I_0x19.getVersion()); - assertW3IVersion("1.31", W3I.EncodingFormat.W3I_0x1C.getVersion()); - assertW3IVersion("1.32", W3I.EncodingFormat.W3I_0x1F.getVersion()); - assertW3IVersion("2.0", W3I.EncodingFormat.W3I_0x21.getVersion()); - assertW3IVersion("3.0", W3I.EncodingFormat.W3I_0x21.getVersion()); + public void pinnedPatchDoesNotChangeSourceW3iFormat() throws Exception { + assertW3IVersion("1.30", W3I.EncodingFormat.W3I_0x27.getVersion()); + assertW3IVersion("1.31", W3I.EncodingFormat.W3I_0x27.getVersion()); + assertW3IVersion("1.32", W3I.EncodingFormat.W3I_0x27.getVersion()); + assertW3IVersion("2.0", W3I.EncodingFormat.W3I_0x27.getVersion()); + assertW3IVersion("3.0", W3I.EncodingFormat.W3I_0x27.getVersion()); } @Test From fd30aca27cd6b1d9ad6a49a18bd0a2557b073a1d Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 1 Oct 2026 11:23:23 +0200 Subject: [PATCH 3/4] Use latest wc3libs W3I serialization --- de.peeeq.wurstscript/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/de.peeeq.wurstscript/build.gradle b/de.peeeq.wurstscript/build.gradle index 6360b413c..5d84a962c 100644 --- a/de.peeeq.wurstscript/build.gradle +++ b/de.peeeq.wurstscript/build.gradle @@ -156,7 +156,7 @@ dependencies { implementation 'com.github.albfernandez:juniversalchardet:2.4.0' implementation 'org.xerial:sqlite-jdbc:3.46.1.3' implementation 'com.github.inwc3:JMPQ3:v2.0.1' - implementation 'com.github.inwc3:wc3libs:86eb1a1b966' + implementation 'com.github.inwc3:wc3libs:df666247c3' implementation 'com.github.wurstscript:wurst-project-config:348fcd4ef5' implementation 'org.slf4j:slf4j-api:2.0.17' implementation 'ch.qos.logback:logback-classic:1.5.20' From fd8103d66600e1f56a42c9e4af5badcf4e0cbd47 Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 1 Oct 2026 11:48:51 +0200 Subject: [PATCH 4/4] Pin merged wc3libs AS_DEFINED support --- de.peeeq.wurstscript/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/de.peeeq.wurstscript/build.gradle b/de.peeeq.wurstscript/build.gradle index 5d84a962c..ff3d4a93d 100644 --- a/de.peeeq.wurstscript/build.gradle +++ b/de.peeeq.wurstscript/build.gradle @@ -156,7 +156,7 @@ dependencies { implementation 'com.github.albfernandez:juniversalchardet:2.4.0' implementation 'org.xerial:sqlite-jdbc:3.46.1.3' implementation 'com.github.inwc3:JMPQ3:v2.0.1' - implementation 'com.github.inwc3:wc3libs:df666247c3' + implementation 'com.github.inwc3:wc3libs:0542c41400' implementation 'com.github.wurstscript:wurst-project-config:348fcd4ef5' implementation 'org.slf4j:slf4j-api:2.0.17' implementation 'ch.qos.logback:logback-classic:1.5.20'