Add support for reading wudmad archive files

This commit is contained in:
Maschell 2020-12-20 15:07:53 +01:00
parent 75a280ece5
commit 07430cc2c7
4 changed files with 205 additions and 3 deletions

View File

@ -102,7 +102,7 @@ public final class WUDService {
WUDImageCompressedInfo info = new WUDImageCompressedInfo(WUDImageCompressedInfo.SECTOR_SIZE, 0, toReadFilesize);
byte[] header = info.getHeaderAsBytes();
log.info("Writing header + " + header.length);
log.info("Writing header");
fileOutput.write(header);
int sectorTableEntryCount = (int) ((toReadFilesize + WUDImageCompressedInfo.SECTOR_SIZE - 1) / (long) WUDImageCompressedInfo.SECTOR_SIZE);

View File

@ -27,6 +27,7 @@ import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader;
import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReaderCompressed;
import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReaderSplitted;
import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReaderUncompressed;
import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReaderWumadArchive;
import de.mas.wiiu.jnus.utils.ByteUtils;
import lombok.Getter;
import lombok.Setter;
@ -41,6 +42,7 @@ public class WUDImage {
@Getter private final boolean isCompressed;
@Getter private final boolean isSplitted;
@Getter private final boolean isWUMADArchiveFormat;
private long inputFileSize = 0L;
@Getter private final WUDDiscReader WUDDiscReader;
@ -57,6 +59,9 @@ public class WUDImage {
RandomAccessFile fileStream = new RandomAccessFile(file, "r");
fileStream.seek(0);
this.fileHandle = file;
byte[] wuxheader = new byte[WUDImageCompressedInfo.WUX_HEADER_SIZE];
fileStream.read(wuxheader);
WUDImageCompressedInfo compressedInfo = new WUDImageCompressedInfo(wuxheader);
@ -65,6 +70,7 @@ public class WUDImage {
log.fine("Image is compressed");
this.isCompressed = true;
this.isSplitted = false;
this.isWUMADArchiveFormat = false;
Map<Integer, Long> indexTable = new HashMap<>();
long offsetIndexTable = compressedInfo.getOffsetIndexTable();
fileStream.seek(offsetIndexTable);
@ -80,6 +86,12 @@ public class WUDImage {
setCompressedInfo(compressedInfo);
} else {
this.isCompressed = false;
int magic0 = ByteUtils.getIntFromBytes(wuxheader, 0x00, ByteOrder.BIG_ENDIAN);
if (magic0 == 0x57696920) {
this.isWUMADArchiveFormat = true;
}else {
this.isWUMADArchiveFormat = false;
}
if (file.getName().equals(String.format(WUDDiscReaderSplitted.WUD_SPLITTED_DEFAULT_FILEPATTERN, 1))
&& (file.length() == WUDDiscReaderSplitted.WUD_SPLITTED_FILE_SIZE)) {
this.isSplitted = true;
@ -93,12 +105,13 @@ public class WUDImage {
this.WUDDiscReader = new WUDDiscReaderCompressed(this, readOffset);
} else if (isSplitted()) {
this.WUDDiscReader = new WUDDiscReaderSplitted(this, readOffset);
} else if ((isWUMADArchiveFormat())) {
this.WUDDiscReader = new WUDDiscReaderWumadArchive(this, readOffset);
} else {
this.WUDDiscReader = new WUDDiscReaderUncompressed(this, readOffset);
}
fileStream.close();
this.fileHandle = file;
}
public long getWUDFileSize() {
@ -107,6 +120,8 @@ public class WUDImage {
inputFileSize = calculateSplittedFileSize();
} else if (isCompressed()) {
inputFileSize = getCompressedInfo().getUncompressedSize();
} else if (isWUMADArchiveFormat()){
inputFileSize = WUDImage.WUD_FILESIZE;
} else {
inputFileSize = getFileHandle().length();
}

View File

@ -0,0 +1,176 @@
/****************************************************************************
* Copyright (C) 2016-2019 Maschell
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/
package de.mas.wiiu.jnus.implementations.wud.reader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.TreeMap;
import de.mas.wiiu.jnus.implementations.wud.WUDImage;
import de.mas.wiiu.jnus.utils.ByteUtils;
import de.mas.wiiu.jnus.utils.StreamUtils;
import lombok.val;
public class WUDDiscReaderWumadArchive extends WUDDiscReader {
private final Map<Long, WUMADAOffsetInfo> offsetMap = new TreeMap<>();
private final int WUMADA_HEADER_SIZE = 0x10000;
public WUDDiscReaderWumadArchive(WUDImage image, long baseOffset) throws IOException {
super(image, baseOffset);
FileInputStream input = new FileInputStream(getImage().getFileHandle());
StreamUtils.skipExactly(input, 0 + this.getBaseOffset());
byte[] rawHeader = StreamUtils.getBytesFromStream(input, WUMADA_HEADER_SIZE);
int fileTableOffset = 0x1000;
int sizeSectorSize = 0x8000;
int offsetSectorSize = 0x800;
long curOffsetInFile = fileTableOffset;
long curOffset = WUMADA_HEADER_SIZE;
// Mapping.
while (true) {
long curValue = ByteUtils.getUnsingedIntFromBytes(rawHeader, (int) curOffsetInFile, ByteOrder.LITTLE_ENDIAN);
if (curValue == 0 && curOffsetInFile != fileTableOffset) {
break;
}
curOffsetInFile += 4;
long size = ByteUtils.getUnsingedIntFromBytes(rawHeader, (int) curOffsetInFile, ByteOrder.LITTLE_ENDIAN);
curOffsetInFile += 4;
offsetMap.put(curValue * offsetSectorSize, new WUMADAOffsetInfo(curOffset, size * sizeSectorSize, curValue * offsetSectorSize, false));
curOffset += size * sizeSectorSize;
}
// Fill in empty regions
long calculatedOffset = 0;
Map<Long, WUMADAOffsetInfo> offsetMapEmptyRegions = new TreeMap<>();
long lastEntry = 0;
for (val curEntry : offsetMap.entrySet()) {
long offsetOnDisc = curEntry.getKey();
if (offsetOnDisc != calculatedOffset) {
offsetMapEmptyRegions.put(calculatedOffset, new WUMADAOffsetInfo(0, offsetOnDisc - calculatedOffset, calculatedOffset, true));
calculatedOffset += offsetOnDisc - calculatedOffset;
}
calculatedOffset += curEntry.getValue().getSize();
lastEntry = calculatedOffset;
}
if (lastEntry < WUDImage.WUD_FILESIZE) {
offsetMap.put(lastEntry, new WUMADAOffsetInfo(0, WUDImage.WUD_FILESIZE - lastEntry, lastEntry, true));
}
offsetMap.putAll(offsetMapEmptyRegions);
}
@Override
public long readEncryptedToStream(OutputStream outputStream, long offset, long size) throws IOException {
FileInputStream input = new FileInputStream(getImage().getFileHandle());
WUMADAOffsetInfo curOffsetInfo = getOffsetInfoForOffset(offset).orElseThrow(() -> new IOException("Invalid read"));
long targetOffsetOfSectionStart = curOffsetInfo.getTargetOffset();
long inSectionOffset = offset - targetOffsetOfSectionStart;
if (inSectionOffset > curOffsetInfo.getSize()) {
throw new IOException("offset > size");
}
long realOffset = inSectionOffset + curOffsetInfo.getOffset();
StreamUtils.skipExactly(input, realOffset + this.getBaseOffset());
int bufferSize = 0x8000;
byte[] buffer = new byte[bufferSize];
long totalread = 0;
long readInSection = 0;
long maximumToRead = curOffsetInfo.getSize() - inSectionOffset;
do {
int read = 0;
if (curOffsetInfo.isEmptyContent()) {
Arrays.fill(buffer, (byte) 0);
read = bufferSize;
} else {
read = input.read(buffer);
if (read < 0) {
break;
}
}
readInSection += read;
// System.out.println(readInSection + " read in section " + maximumToRead);
if (readInSection >= maximumToRead) {
read -= (int) (readInSection - maximumToRead);
long offsetRead = offset + totalread + read;
if (offsetRead < offset + size) {
curOffsetInfo = getOffsetInfoForOffset(offsetRead).orElseThrow(() -> new IOException("Invalid read for offset " + offsetRead));
maximumToRead = curOffsetInfo.getSize();
readInSection = 0;
input.close();
input = new FileInputStream(getImage().getFileHandle());
StreamUtils.skipExactly(input, curOffsetInfo.getOffset() + this.getBaseOffset());
}
}
if (totalread + read > size) {
read = (int) (size - totalread);
}
try {
outputStream.write(Arrays.copyOfRange(buffer, 0, read));
} catch (IOException e) {
if (e.getMessage().equals("Pipe closed")) {
break;
} else {
input.close();
throw e;
}
}
totalread += read;
} while (totalread < size);
input.close();
outputStream.close();
return totalread;
}
private Optional<WUMADAOffsetInfo> getOffsetInfoForOffset(long offset) {
Optional<WUMADAOffsetInfo> curOffsetInfo = Optional.empty();
for (Entry<Long, WUMADAOffsetInfo> test : offsetMap.entrySet()) {
val start = test.getValue().getTargetOffset();
val end = start + test.getValue().getSize();
if (offset < start) {
continue;
}
if (offset < end) {
curOffsetInfo = Optional.of(test.getValue());
break;
}
}
return curOffsetInfo;
}
}

View File

@ -0,0 +1,11 @@
package de.mas.wiiu.jnus.implementations.wud.reader;
import lombok.Data;
@Data
public class WUMADAOffsetInfo {
private final long offset;
private final long size;
private final long targetOffset;
private final boolean emptyContent;
}