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 parent; private final NUSTitle title; public NUSTitleEncryptedFuseContainer(Optional parent, NUSTitle t) { this.parent = parent; this.title = t; } @Override public Optional getParent() { return parent; } private Optional 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 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 hash = title.getDataProvider().getContentH3Hash(c); return hash; } catch (IOException e) { } } return Optional.empty(); }); } private Optional getTMDforPath(String path) { return getFileforPath(path, "title.tmd", () -> { try { return title.getDataProvider().getRawTMD(); } catch (IOException e) { return Optional.empty(); } }); } private Optional getTicketforPath(String path) { return getFileforPath(path, "title.tik", () -> { try { return title.getDataProvider().getRawTicket(); } catch (IOException e) { return Optional.empty(); } }); } private Optional getCertforPath(String path) { return getFileforPath(path, "title.cert", () -> { try { return title.getDataProvider().getRawCert(); } catch (IOException e) { return Optional.empty(); } }); } private Optional getFileforPath(String path, String expected, Supplier> 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 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 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>> functions = new ArrayList<>(); String pathcopy = path; functions.add(() -> getTMDforPath(pathcopy)); functions.add(() -> getTicketforPath(pathcopy)); functions.add(() -> getCertforPath(pathcopy)); for (Supplier> func : functions) { Optional 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 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 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>> functions = new ArrayList<>(); String pathcopy = path; functions.add(() -> getTMDforPath(pathcopy)); functions.add(() -> getTicketforPath(pathcopy)); functions.add(() -> getCertforPath(pathcopy)); for (Supplier> func : functions) { Optional 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() { } }