fuse-wiiu/src/main/java/de/mas/wiiu/jnus/fuse_wiiu/implementation/NUSTitleEncryptedFuseContai...

270 lines
8.3 KiB
Java

package de.mas.wiiu.jnus.fuse_wiiu.implementation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import de.mas.wiiu.jnus.NUSTitle;
import de.mas.wiiu.jnus.entities.content.Content;
import de.mas.wiiu.jnus.fuse_wiiu.interfaces.FuseContainer;
import de.mas.wiiu.jnus.fuse_wiiu.interfaces.FuseDirectory;
import jnr.ffi.Pointer;
import ru.serce.jnrfuse.ErrorCodes;
import ru.serce.jnrfuse.FuseFillDir;
import ru.serce.jnrfuse.struct.FileStat;
import ru.serce.jnrfuse.struct.FuseFileInfo;
public class NUSTitleEncryptedFuseContainer implements FuseContainer {
private final Optional<FuseDirectory> parent;
private final NUSTitle title;
public NUSTitleEncryptedFuseContainer(Optional<FuseDirectory> parent, NUSTitle t) {
this.parent = parent;
this.title = t;
}
@Override
public Optional<FuseDirectory> getParent() {
return parent;
}
private Optional<Content> getContentForPath(String path) {
if (!path.endsWith(".app") || path.length() != 12) {
return Optional.empty();
}
try {
int contentID = Integer.parseInt(path.substring(0, 8), 16);
Content c = title.getTMD().getContentByID(contentID);
if (c != null) {
return Optional.of(c);
}
} catch (NumberFormatException e) {
}
return Optional.empty();
}
@SuppressWarnings("unused")
private Optional<byte[]> getH3ForPath(String path) {
if (!path.endsWith(".h3") || path.length() != 11) {
return Optional.empty();
}
return getContentForPath(path.substring(0, 8) + ".app").flatMap(c -> {
if (c.isHashed()) {
try {
Optional<byte[]> hash = title.getDataProvider().getContentH3Hash(c);
return hash;
} catch (IOException e) {
}
}
return Optional.empty();
});
}
private Optional<byte[]> getTMDforPath(String path) {
return getFileforPath(path, "title.tmd", () -> {
try {
return title.getDataProvider().getRawTMD();
} catch (IOException e) {
return Optional.empty();
}
});
}
private Optional<byte[]> getTicketforPath(String path) {
return getFileforPath(path, "title.tik", () -> {
try {
return title.getDataProvider().getRawTicket();
} catch (IOException e) {
return Optional.empty();
}
});
}
private Optional<byte[]> getCertforPath(String path) {
return getFileforPath(path, "title.cert", () -> {
try {
return title.getDataProvider().getRawCert();
} catch (IOException e) {
return Optional.empty();
}
});
}
private Optional<byte[]> getFileforPath(String path, String expected, Supplier<Optional<byte[]>> func) {
if (!path.equals(expected)) {
return Optional.empty();
}
return func.get();
}
@Override
public int open(String path, FuseFileInfo fi) {
if (path.equals("/")) {
return -ErrorCodes.EISDIR();
}
return getattr(path, null);
}
@Override
public int getattr(String path, FileStat stat) {
if (path.equals("/")) {
stat.st_mode.set(FileStat.S_IFDIR | FileStat.ALL_READ);
stat.st_nlink.set(2);
return 0;
}
path = path.substring(1);
Optional<Content> coOptional = getContentForPath(path);
if (coOptional.isPresent()) {
if (stat != null) {
stat.st_mode.set(FileStat.S_IFREG | FileStat.ALL_READ);
stat.st_nlink.set(1);
stat.st_size.set(coOptional.get().getEncryptedFileSize());
}
return 0;
} else {
Optional<byte[]> h3Data = getH3ForPath(path);
if (h3Data.isPresent()) {
if (stat != null) {
stat.st_mode.set(FileStat.S_IFREG | FileStat.ALL_READ);
stat.st_nlink.set(1);
stat.st_size.set(h3Data.get().length);
}
return 0;
}
}
List<Supplier<Optional<byte[]>>> functions = new ArrayList<>();
String pathcopy = path;
functions.add(() -> getTMDforPath(pathcopy));
functions.add(() -> getTicketforPath(pathcopy));
functions.add(() -> getCertforPath(pathcopy));
for (Supplier<Optional<byte[]>> func : functions) {
Optional<byte[]> data = func.get();
if (data.isPresent()) {
if (stat != null) {
stat.st_mode.set(FileStat.S_IFREG | FileStat.ALL_READ);
stat.st_nlink.set(1);
stat.st_size.set(data.get().length);
}
return 0;
}
}
return -ErrorCodes.ENOENT();
}
@Override
public int readdir(String path, Pointer buf, FuseFillDir filter, long offset, FuseFileInfo fi) {
for (Content e : title.getTMD().getAllContents().values()) {
filter.apply(buf, e.getFilename(), null, 0);
if (e.isHashed()) {
filter.apply(buf, String.format("%08X.h3", e.getID()), null, 0);
}
}
if (getTMDforPath("title.tmd").isPresent()) {
filter.apply(buf, "title.tmd", null, 0);
}
if (getTicketforPath("title.tik").isPresent()) {
filter.apply(buf, "title.tik", null, 0);
}
if (getCertforPath("title.cert").isPresent()) {
filter.apply(buf, "title.cert", null, 0);
}
return 0;
}
@Override
public int read(String path, Pointer buf, long size, long offset, FuseFileInfo fi) {
if (path.equals("/")) {
return -ErrorCodes.EISDIR();
}
path = path.substring(1);
Optional<Content> coOptional = getContentForPath(path);
if (coOptional.isPresent()) {
Content c = coOptional.get();
if (offset >= c.getEncryptedFileSize()) {
return -ErrorCodes.ENOENT();
}
if (offset + size > c.getEncryptedFileSize()) {
size = c.getEncryptedFileSize() - offset;
}
byte[] data;
try {
data = title.getDataProvider().getChunkFromContent(c, offset, (int) size);
buf.put(0, data, 0, data.length);
return data.length;
} catch (Exception e) {
e.printStackTrace();
return -ErrorCodes.ENOENT();
}
} else {
Optional<byte[]> h3Data = getH3ForPath(path);
if (h3Data.isPresent()) {
byte[] hash = h3Data.get();
if (offset >= hash.length) {
return -ErrorCodes.ENOENT();
}
if (offset + size > hash.length) {
size = hash.length - offset;
}
buf.put(0, hash, (int) offset, (int) size);
return (int) size;
}
}
// Check if the tmd ticket or cert are request.
List<Supplier<Optional<byte[]>>> functions = new ArrayList<>();
String pathcopy = path;
functions.add(() -> getTMDforPath(pathcopy));
functions.add(() -> getTicketforPath(pathcopy));
functions.add(() -> getCertforPath(pathcopy));
for (Supplier<Optional<byte[]>> func : functions) {
Optional<byte[]> dataOpt = func.get();
if (dataOpt.isPresent()) {
byte[] data = dataOpt.get();
if (data == null || data.length == 0) {
return -ErrorCodes.ENOENT();
}
if (offset >= data.length) {
return -ErrorCodes.ENOENT();
}
if (offset + size > data.length) {
size = data.length - offset;
}
buf.put(0, data, (int) offset, (int) size);
return (int) size;
}
}
return 0;
}
@Override
public void init() {
}
@Override
public void deinit() {
}
}