mirror of
https://github.com/Maschell/JNUSLib.git
synced 2024-06-28 11:56:03 +02:00
Add support for reading wudmad archive files
This commit is contained in:
parent
75a280ece5
commit
07430cc2c7
|
@ -102,7 +102,7 @@ public final class WUDService {
|
||||||
WUDImageCompressedInfo info = new WUDImageCompressedInfo(WUDImageCompressedInfo.SECTOR_SIZE, 0, toReadFilesize);
|
WUDImageCompressedInfo info = new WUDImageCompressedInfo(WUDImageCompressedInfo.SECTOR_SIZE, 0, toReadFilesize);
|
||||||
|
|
||||||
byte[] header = info.getHeaderAsBytes();
|
byte[] header = info.getHeaderAsBytes();
|
||||||
log.info("Writing header + " + header.length);
|
log.info("Writing header");
|
||||||
fileOutput.write(header);
|
fileOutput.write(header);
|
||||||
|
|
||||||
int sectorTableEntryCount = (int) ((toReadFilesize + WUDImageCompressedInfo.SECTOR_SIZE - 1) / (long) WUDImageCompressedInfo.SECTOR_SIZE);
|
int sectorTableEntryCount = (int) ((toReadFilesize + WUDImageCompressedInfo.SECTOR_SIZE - 1) / (long) WUDImageCompressedInfo.SECTOR_SIZE);
|
||||||
|
|
|
@ -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.WUDDiscReaderCompressed;
|
||||||
import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReaderSplitted;
|
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.WUDDiscReaderUncompressed;
|
||||||
|
import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReaderWumadArchive;
|
||||||
import de.mas.wiiu.jnus.utils.ByteUtils;
|
import de.mas.wiiu.jnus.utils.ByteUtils;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
@ -41,6 +42,7 @@ public class WUDImage {
|
||||||
|
|
||||||
@Getter private final boolean isCompressed;
|
@Getter private final boolean isCompressed;
|
||||||
@Getter private final boolean isSplitted;
|
@Getter private final boolean isSplitted;
|
||||||
|
@Getter private final boolean isWUMADArchiveFormat;
|
||||||
|
|
||||||
private long inputFileSize = 0L;
|
private long inputFileSize = 0L;
|
||||||
@Getter private final WUDDiscReader WUDDiscReader;
|
@Getter private final WUDDiscReader WUDDiscReader;
|
||||||
|
@ -57,6 +59,9 @@ public class WUDImage {
|
||||||
|
|
||||||
RandomAccessFile fileStream = new RandomAccessFile(file, "r");
|
RandomAccessFile fileStream = new RandomAccessFile(file, "r");
|
||||||
fileStream.seek(0);
|
fileStream.seek(0);
|
||||||
|
|
||||||
|
this.fileHandle = file;
|
||||||
|
|
||||||
byte[] wuxheader = new byte[WUDImageCompressedInfo.WUX_HEADER_SIZE];
|
byte[] wuxheader = new byte[WUDImageCompressedInfo.WUX_HEADER_SIZE];
|
||||||
fileStream.read(wuxheader);
|
fileStream.read(wuxheader);
|
||||||
WUDImageCompressedInfo compressedInfo = new WUDImageCompressedInfo(wuxheader);
|
WUDImageCompressedInfo compressedInfo = new WUDImageCompressedInfo(wuxheader);
|
||||||
|
@ -65,6 +70,7 @@ public class WUDImage {
|
||||||
log.fine("Image is compressed");
|
log.fine("Image is compressed");
|
||||||
this.isCompressed = true;
|
this.isCompressed = true;
|
||||||
this.isSplitted = false;
|
this.isSplitted = false;
|
||||||
|
this.isWUMADArchiveFormat = false;
|
||||||
Map<Integer, Long> indexTable = new HashMap<>();
|
Map<Integer, Long> indexTable = new HashMap<>();
|
||||||
long offsetIndexTable = compressedInfo.getOffsetIndexTable();
|
long offsetIndexTable = compressedInfo.getOffsetIndexTable();
|
||||||
fileStream.seek(offsetIndexTable);
|
fileStream.seek(offsetIndexTable);
|
||||||
|
@ -80,6 +86,12 @@ public class WUDImage {
|
||||||
setCompressedInfo(compressedInfo);
|
setCompressedInfo(compressedInfo);
|
||||||
} else {
|
} else {
|
||||||
this.isCompressed = false;
|
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))
|
if (file.getName().equals(String.format(WUDDiscReaderSplitted.WUD_SPLITTED_DEFAULT_FILEPATTERN, 1))
|
||||||
&& (file.length() == WUDDiscReaderSplitted.WUD_SPLITTED_FILE_SIZE)) {
|
&& (file.length() == WUDDiscReaderSplitted.WUD_SPLITTED_FILE_SIZE)) {
|
||||||
this.isSplitted = true;
|
this.isSplitted = true;
|
||||||
|
@ -93,12 +105,13 @@ public class WUDImage {
|
||||||
this.WUDDiscReader = new WUDDiscReaderCompressed(this, readOffset);
|
this.WUDDiscReader = new WUDDiscReaderCompressed(this, readOffset);
|
||||||
} else if (isSplitted()) {
|
} else if (isSplitted()) {
|
||||||
this.WUDDiscReader = new WUDDiscReaderSplitted(this, readOffset);
|
this.WUDDiscReader = new WUDDiscReaderSplitted(this, readOffset);
|
||||||
|
} else if ((isWUMADArchiveFormat())) {
|
||||||
|
this.WUDDiscReader = new WUDDiscReaderWumadArchive(this, readOffset);
|
||||||
} else {
|
} else {
|
||||||
this.WUDDiscReader = new WUDDiscReaderUncompressed(this, readOffset);
|
this.WUDDiscReader = new WUDDiscReaderUncompressed(this, readOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
fileStream.close();
|
fileStream.close();
|
||||||
this.fileHandle = file;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getWUDFileSize() {
|
public long getWUDFileSize() {
|
||||||
|
@ -107,6 +120,8 @@ public class WUDImage {
|
||||||
inputFileSize = calculateSplittedFileSize();
|
inputFileSize = calculateSplittedFileSize();
|
||||||
} else if (isCompressed()) {
|
} else if (isCompressed()) {
|
||||||
inputFileSize = getCompressedInfo().getUncompressedSize();
|
inputFileSize = getCompressedInfo().getUncompressedSize();
|
||||||
|
} else if (isWUMADArchiveFormat()){
|
||||||
|
inputFileSize = WUDImage.WUD_FILESIZE;
|
||||||
} else {
|
} else {
|
||||||
inputFileSize = getFileHandle().length();
|
inputFileSize = getFileHandle().length();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user