Skip to content

Commit 73c59e4

Browse files
committed
Handle MPQ maps without HM3W headers
1 parent 3effd09 commit 73c59e4

2 files changed

Lines changed: 77 additions & 5 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -358,18 +358,37 @@ private static void setVolatilePlayerConfig(WurstProjectBuildPlayer wplayer, W3I
358358

359359
private static void applyMapHeader(WurstProjectConfigData projectConfig, File targetMap) throws IOException {
360360
boolean shouldWrite = false;
361-
MapHeader mapHeader = MapHeader.ofFile(targetMap);
362-
if (!projectConfig.buildMapData().players().isEmpty()) {
363-
mapHeader.setMaxPlayersCount(projectConfig.buildMapData().players().size());
361+
WurstProjectBuildMapData buildMapData = projectConfig.buildMapData();
362+
if (buildMapData.players().isEmpty() && StringUtils.isBlank(buildMapData.name())) {
363+
return;
364+
}
365+
366+
// A Warcraft III map may omit the optional 512-byte HM3W prefix and start
367+
// directly with its MPQ archive. MapHeader.ofFile only reads the prefix,
368+
// so use a new header in that case; writeToMapFile will insert it before
369+
// the archive.
370+
MapHeader mapHeader = startsWithMpqArchive(targetMap)
371+
? new MapHeader()
372+
: MapHeader.ofFile(targetMap);
373+
if (!buildMapData.players().isEmpty()) {
374+
mapHeader.setMaxPlayersCount(buildMapData.players().size());
364375
shouldWrite = true;
365376
}
366-
if (StringUtils.isNotBlank(projectConfig.buildMapData().name())) {
367-
mapHeader.setMapName(projectConfig.buildMapData().name());
377+
if (StringUtils.isNotBlank(buildMapData.name())) {
378+
mapHeader.setMapName(buildMapData.name());
368379
shouldWrite = true;
369380
}
370381
if (shouldWrite) {
371382
WLogger.info("Applying map header");
372383
mapHeader.writeToMapFile(targetMap);
373384
}
374385
}
386+
387+
private static boolean startsWithMpqArchive(File targetMap) throws IOException {
388+
try (InputStream input = new FileInputStream(targetMap)) {
389+
byte[] startToken = input.readNBytes(4);
390+
return startToken.length == 4
391+
&& new String(startToken, StandardCharsets.US_ASCII).startsWith("MPQ");
392+
}
393+
}
375394
}

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstBuildConfigTests.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package tests.wurstscript.tests;
22

33
import org.wurstscript.projectconfig.WurstProjectConfigData;
4+
import org.wurstscript.projectconfig.WurstProjectBuildMapData;
45
import de.peeeq.wurstio.languageserver.WFile;
56
import de.peeeq.wurstio.languageserver.ProjectConfigBuilder;
67
import de.peeeq.wurstio.languageserver.WurstBuildConfig;
78
import de.peeeq.wurstio.languageserver.WurstCommands;
89
import de.peeeq.wurstio.utils.W3InstallationData;
10+
import net.moonlightflower.wc3libs.bin.app.MapHeader;
911
import net.moonlightflower.wc3libs.port.GameVersion;
1012
import org.testng.annotations.Test;
1113

@@ -223,6 +225,57 @@ public void configInjectionPrefersPinnedPatchOverDetectedInstallVersion() throws
223225
);
224226
}
225227

228+
@Test
229+
public void mapHeaderConfigAcceptsAnArchiveWithoutAnHm3wPrefix() throws Exception {
230+
Path mapWithoutHeader = Files.createTempFile("wurst-map-without-header", ".w3x");
231+
byte[] original = new byte[1024];
232+
original[0] = 'M';
233+
original[1] = 'P';
234+
original[2] = 'Q';
235+
original[3] = 0x1a;
236+
Files.write(mapWithoutHeader, original);
237+
238+
Method applyMapHeader = ProjectConfigBuilder.class.getDeclaredMethod(
239+
"applyMapHeader", WurstProjectConfigData.class, File.class
240+
);
241+
applyMapHeader.setAccessible(true);
242+
243+
applyMapHeader.invoke(
244+
null,
245+
new WurstProjectConfigData(
246+
"Test",
247+
List.of(),
248+
new WurstProjectBuildMapData("Configured map", null, null, null, null, List.of(), List.of()),
249+
null,
250+
null
251+
),
252+
mapWithoutHeader.toFile()
253+
);
254+
255+
assertEquals(Files.readAllBytes(mapWithoutHeader)[0], (byte) 'H');
256+
assertEquals(MapHeader.ofFile(mapWithoutHeader.toFile()).getMaxPlayersCount(), 0);
257+
assertEquals(Files.size(mapWithoutHeader), original.length + 512);
258+
}
259+
260+
@Test
261+
public void mapHeaderConfigDoesNotReadAnArchiveWhenNothingNeedsChanging() throws Exception {
262+
Path mapWithoutHeader = Files.createTempFile("wurst-map-without-header-noop", ".w3x");
263+
byte[] original = new byte[1024];
264+
original[0] = 'M';
265+
original[1] = 'P';
266+
original[2] = 'Q';
267+
original[3] = 0x1a;
268+
Files.write(mapWithoutHeader, original);
269+
270+
Method applyMapHeader = ProjectConfigBuilder.class.getDeclaredMethod(
271+
"applyMapHeader", WurstProjectConfigData.class, File.class
272+
);
273+
applyMapHeader.setAccessible(true);
274+
applyMapHeader.invoke(null, WurstProjectConfigData.empty(), mapWithoutHeader.toFile());
275+
276+
assertEquals(Files.readAllBytes(mapWithoutHeader), original);
277+
}
278+
226279
private static String calculateProjectConfigHash(File buildDir) throws Exception {
227280
Method method = ProjectConfigBuilder.class.getDeclaredMethod(
228281
"calculateProjectConfigHash",

0 commit comments

Comments
 (0)