Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow usage of external Listfile to rebuild MPQ. #36

Merged
merged 10 commits into from
Mar 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 75 additions & 12 deletions src/main/java/systems/crigges/jmpq3/JMpqEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -209,25 +209,48 @@ private void readAttributesFile() {
}
}

/**
* For use when the MPQ is missing a (listfile)
* Adds this custom listfile into the MPQ and uses it
* for rebuilding purposes.
* If this is not a full listfile, the end result will be missing files.
*
* @param externalListfilePath Path to a file containing listfile entries
*/
public void setExternalListfile(File externalListfilePath) {
if(!canWrite) {
log.warn("The mpq was opened as readonly, setting an external listfile will have no effect.");
return;
}
if(!externalListfilePath.exists()) {
log.warn("External MPQ File: " + externalListfilePath.getAbsolutePath() +
" does not exist and will not be used");
return;
}
try {
// Read and apply listfile
listFile = new Listfile(Files.readAllBytes(externalListfilePath.toPath()));
checkListfileEntries();
// Operation succeeded and added a listfile so we can now write properly.
// (as long as it wasn't read-only to begin with)
} catch (Exception ex) {
log.warn("Could not apply external listfile: " + externalListfilePath.getAbsolutePath());
// The value of canWrite is not changed intentionally
}
}

/**
* Reads an internal Listfile name called (listfile)
* and applies that as the archive's listfile.
*/
private void readListFile() {
if (hasFile("(listfile)")) {
try {
File tempFile = File.createTempFile("list", "file", JMpqEditor.tempDir);
tempFile.deleteOnExit();
extractFile("(listfile)", tempFile);
listFile = new Listfile(Files.readAllBytes(tempFile.toPath()));
int hiddenFiles = (hasFile("(attributes)") ? 2 : 1) + (hasFile("(signature)") ? 1 : 0);
if (canWrite) {
if (listFile.getFiles().size() >= blockTable.getAllVaildBlocks().size() - hiddenFiles) {
log.warn("mpq's listfile is incomplete. Blocks without listfile entry will be discarded");
}
for (String fileName : listFile.getFiles()) {
if (!hasFile(fileName)) {
log.warn("listfile entry does not exist in archive and will be discarded: " + fileName);
}
}
listFile.getFileMap().entrySet().removeIf(file -> !hasFile(file.getValue()));
}
checkListfileEntries();
} catch (Exception e) {
log.warn("Extracting the mpq's listfile failed. It cannot be rebuild.", e);
}
Expand All @@ -237,6 +260,37 @@ private void readListFile() {
}
}

/**
* Performs verification to see if we know all the blocks of this file.
* Prints warnings if we don't know all blocks.
*
* @throws JMpqException If retrieving valid blocks fails
*/
private void checkListfileEntries() throws JMpqException {
int hiddenFiles = (hasFile("(attributes)") ? 2 : 1) + (hasFile("(signature)") ? 1 : 0);
if (canWrite) {
checkListfileCompleteness(hiddenFiles);
}
}

/**
* Checks listfile for completeness against block table
*
* @param hiddenFiles Num. hidden files
* @throws JMpqException If retrieving valid blocks fails
*/
private void checkListfileCompleteness(int hiddenFiles) throws JMpqException {
if (listFile.getFiles().size() <= blockTable.getAllVaildBlocks().size() - hiddenFiles) {
log.warn("mpq's listfile is incomplete. Blocks without listfile entry will be discarded");
}
for (String fileName : listFile.getFiles()) {
if (!hasFile(fileName)) {
log.warn("listfile entry does not exist in archive and will be discarded: " + fileName);
}
}
listFile.getFileMap().entrySet().removeIf(file -> !hasFile(file.getValue()));
}

private void readBlockTable() throws IOException {
ByteBuffer blockBuffer = ByteBuffer.allocate(blockSize * 16).order(ByteOrder.LITTLE_ENDIAN);
fc.position(headerOffset + blockPos);
Expand Down Expand Up @@ -1046,4 +1100,13 @@ public String toString() {
return "JMpqEditor [headerSize=" + headerSize + ", archiveSize=" + archiveSize + ", formatVersion=" + formatVersion + ", discBlockSize=" + discBlockSize
+ ", hashPos=" + hashPos + ", blockPos=" + blockPos + ", hashSize=" + hashSize + ", blockSize=" + blockSize + ", hashMap=" + hashTable + "]";
}

/**
* Returns an unmodifiable collection of all Listfile entries
*
* @return Listfile entries
*/
public Collection<String> getListfileEntries() {
return Collections.unmodifiableCollection(listFile.getFiles());
}
}
14 changes: 12 additions & 2 deletions src/test/java/systems/crigges/jmpq3test/MpqTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
import java.nio.file.StandardCopyOption;
import java.util.*;

import static systems.crigges.jmpq3.HashTable.calculateFileKey;

/**
* Created by Frotty on 06.03.2017.
*/
Expand Down Expand Up @@ -136,6 +134,18 @@ public void testRebuild() throws IOException {
}
}

@Test
public void testExternalListfile() throws Exception {
File mpq = getFile("mpqs/normalMap.w3x");
File listFile = getFile("listfile.txt");
JMpqEditor mpqEditor = new JMpqEditor(mpq, MPQOpenOption.FORCE_V0);
if(mpqEditor.isCanWrite()) {
mpqEditor.deleteFile("(listfile)");
}
mpqEditor.setExternalListfile(listFile);
Assert.assertTrue(mpqEditor.getListfileEntries().contains("war3map.w3a"));
}

@Test
public void testRecompressBuild() throws IOException {
File[] mpqs = getMpqs();
Expand Down
6 changes: 6 additions & 0 deletions src/test/resources/listfile.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
scripts\war3map.j
war3map.j
war3map.w3u
war3map.w3t
war3map.w3a
customFile.j