diff --git a/src/de/mas/wiiu/jnus/NUSDataProviderWumad.java b/src/de/mas/wiiu/jnus/NUSDataProviderWumad.java new file mode 100644 index 0000000..d22cd9f --- /dev/null +++ b/src/de/mas/wiiu/jnus/NUSDataProviderWumad.java @@ -0,0 +1,107 @@ +/**************************************************************************** + * 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 . + ****************************************************************************/ +package de.mas.wiiu.jnus; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import de.mas.wiiu.jnus.entities.TMD; +import de.mas.wiiu.jnus.entities.content.Content; +import de.mas.wiiu.jnus.implementations.wud.parser.WUDPartitionHeader; +import de.mas.wiiu.jnus.implementations.wumad.WumadInfo; +import de.mas.wiiu.jnus.interfaces.NUSDataProvider; +import de.mas.wiiu.jnus.utils.StreamUtils; + +public class NUSDataProviderWumad implements NUSDataProvider { + + private final WumadInfo info; + + private final Map files = new HashMap<>(); + + public NUSDataProviderWumad(WumadInfo info) { + this.info = info; + } + + private Map loadFileList(ZipFile zipFile) { + Enumeration zipEntries = zipFile.entries(); + Map result = new HashMap<>(); + while (zipEntries.hasMoreElements()) { + ZipEntry entry = (ZipEntry) zipEntries.nextElement(); + if (!entry.isDirectory()) { + String entryName = entry.getName(); + result.put(entryName.toLowerCase(Locale.ENGLISH), entry); + } + } + return result; + } + + @Override + public InputStream readContentAsStream(Content content, long offset, long size) throws IOException { + if (files.isEmpty()) { + files.putAll(loadFileList(info.getZipFile())); + } + + ZipEntry entry = files.values().stream().filter(e -> e.getName().startsWith("p" + info.getPartition() + ".")) + .filter(e -> e.getName().endsWith(content.getFilename().toLowerCase())).findFirst().orElseThrow(() -> new FileNotFoundException()); + InputStream in = info.getZipFile().getInputStream(entry); + StreamUtils.skipExactly(in, offset); + + return in; + } + + @Override + public Optional getContentH3Hash(Content content) throws IOException { + WUDPartitionHeader partitionHeader = info.getPartitionHeader(); + if (!partitionHeader.isCalculatedHashes()) { + try { + partitionHeader.calculateHashes(TMD.parseTMD(getRawTMD().get()).getAllContents()); + } catch (ParseException e) { + throw new IOException(e); + } + } + return partitionHeader.getH3Hash(content); + } + + @Override + public Optional getRawTMD() throws IOException { + return info.getTmdData(); + } + + @Override + public Optional getRawTicket() throws IOException { + return info.getTicketData(); + } + + @Override + public Optional getRawCert() throws IOException { + return info.getCertData(); + } + + @Override + public void cleanup() throws IOException { + info.getZipFile().close(); + } +} diff --git a/src/de/mas/wiiu/jnus/NUSTitleLoaderWumad.java b/src/de/mas/wiiu/jnus/NUSTitleLoaderWumad.java new file mode 100644 index 0000000..6da8619 --- /dev/null +++ b/src/de/mas/wiiu/jnus/NUSTitleLoaderWumad.java @@ -0,0 +1,46 @@ +/**************************************************************************** + * 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 . + ****************************************************************************/ +package de.mas.wiiu.jnus; + +import java.io.File; +import java.io.IOException; +import java.text.ParseException; + +import javax.xml.parsers.ParserConfigurationException; + +import org.xml.sax.SAXException; + +import de.mas.wiiu.jnus.implementations.wumad.WumadInfo; +import de.mas.wiiu.jnus.implementations.wumad.WumadParser; + +public final class NUSTitleLoaderWumad { + + private NUSTitleLoaderWumad() { + + } + + public static NUSTitle loadNUSTitle(File inputFile, byte[] commonKey) throws IOException, ParserConfigurationException, SAXException, ParseException { + NUSTitleConfig config = new NUSTitleConfig(); + + config.setCommonKey(commonKey); + + WumadInfo wumadInfo = WumadParser.createWumadInfo(inputFile); + + return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderWumad(wumadInfo)); + } + +} diff --git a/src/de/mas/wiiu/jnus/implementations/wumad/WumadInfo.java b/src/de/mas/wiiu/jnus/implementations/wumad/WumadInfo.java new file mode 100644 index 0000000..17b66dd --- /dev/null +++ b/src/de/mas/wiiu/jnus/implementations/wumad/WumadInfo.java @@ -0,0 +1,40 @@ +/**************************************************************************** + * 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 . + ****************************************************************************/ +package de.mas.wiiu.jnus.implementations.wumad; + +import java.util.Optional; +import java.util.zip.ZipFile; + +import de.mas.wiiu.jnus.implementations.wud.parser.WUDPartitionHeader; +import lombok.Data; +import lombok.Setter; + +@Data +public class WumadInfo { + + @Setter private Optional tmdData = Optional.empty(); + @Setter private Optional ticketData = Optional.empty(); + @Setter private Optional certData = Optional.empty(); + + @Setter private ZipFile zipFile; + @Setter private WUDPartitionHeader partitionHeader; + + @Setter private String partition; + + WumadInfo() { + } +} diff --git a/src/de/mas/wiiu/jnus/implementations/wumad/WumadParser.java b/src/de/mas/wiiu/jnus/implementations/wumad/WumadParser.java new file mode 100644 index 0000000..69fdd9d --- /dev/null +++ b/src/de/mas/wiiu/jnus/implementations/wumad/WumadParser.java @@ -0,0 +1,97 @@ +/**************************************************************************** + * 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 . + ****************************************************************************/ +package de.mas.wiiu.jnus.implementations.wumad; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.util.Optional; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +import javax.xml.parsers.ParserConfigurationException; + +import org.xml.sax.SAXException; + +import de.mas.wiiu.jnus.entities.fst.FST; +import de.mas.wiiu.jnus.entities.fst.FSTEntry; +import de.mas.wiiu.jnus.implementations.wud.parser.WUDPartitionHeader; +import de.mas.wiiu.jnus.utils.FSTUtils; +import de.mas.wiiu.jnus.utils.StreamUtils; + +public class WumadParser { + + public static final String WUD_TMD_FILENAME = "title.tmd"; + public static final String WUD_TICKET_FILENAME = "title.tik"; + public static final String WUD_CERT_FILENAME = "title.cert"; + + public static final String SI_FST_FILENAME = "sip.fst.00000000.app"; + public static final String P01_HEADER = ""; + + public static WumadInfo createWumadInfo(File wumadFile) throws IOException, ParserConfigurationException, SAXException, ParseException { + WumadInfo result = new WumadInfo(); + + try { + ZipFile zipFile = new ZipFile(wumadFile); + result.setZipFile(zipFile); + + // TODO: some .wumad doesn't have SI files. + ZipEntry fst = zipFile.getEntry(SI_FST_FILENAME); + + byte[] fstBytes = StreamUtils.getBytesFromStream(zipFile.getInputStream(fst), (int) fst.getSize()); + + FST fstdd = FST.parseFST(fstBytes); + + // TODO: add support for multiple partition inside a wumad + FSTEntry dirRoot = fstdd.getRoot().getDirChildren().get(0); + + ZipEntry data = zipFile.getEntry(String.format("sip.s00%s.00000000.app", dirRoot.getFilename())); + + result.setTmdData(getFSTEntryAsByte(dirRoot.getFullPath() + "/" + WUD_TMD_FILENAME, dirRoot, zipFile, data)); + result.setCertData(getFSTEntryAsByte(dirRoot.getFullPath() + "/" + WUD_CERT_FILENAME, dirRoot, zipFile, data)); + result.setTicketData(getFSTEntryAsByte(dirRoot.getFullPath() + "/" + WUD_TICKET_FILENAME, dirRoot, zipFile, data)); + + ZipEntry headerEntry = zipFile.getEntry(String.format("p%s.header.bin", dirRoot.getFilename())); + + byte[] header = StreamUtils.getBytesFromStream(zipFile.getInputStream(headerEntry), (int) headerEntry.getSize()); + + result.setPartitionHeader(WUDPartitionHeader.parseHeader(header)); + result.setPartition(dirRoot.getFilename()); + + } catch (ZipException e) { + e.printStackTrace(); + throw new ParseException("Failed to parse wumad:" + e.getMessage(), 0); + } + + return result; + } + + private static Optional getFSTEntryAsByte(String filePath, FSTEntry dirRoot, ZipFile zipFile, ZipEntry data) throws IOException { + Optional entryOpt = FSTUtils.getEntryByFullPath(dirRoot, filePath); + if (!entryOpt.isPresent()) { + return Optional.empty(); + } + FSTEntry entry = entryOpt.get(); + + InputStream in = zipFile.getInputStream(data); + + StreamUtils.skipExactly(in, entry.getFileOffset()); + return Optional.of(StreamUtils.getBytesFromStream(in, (int) entry.getFileSize())); + } +}