package de.mas.jnustool; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import de.mas.jnustool.util.Util; public class FST { private TitleMetaData tmd; long totalContentSize = 0L; long totalContentSizeInNUS = 0L; List fileEntries = new ArrayList<>(); int totalContentCount = 0; int totalEntries = 0; int dirEntries = 0; public FEntry metaFENtry; public List metaFolder = new ArrayList<>(); private Directory FSTDirectory = new Directory("root"); private Directory contentDirectory = new Directory("root"); public FST(byte[] decrypteddata, TitleMetaData tmd) throws IOException { parse(decrypteddata,tmd); setTmd(tmd); buildDirectories(); } private void buildDirectories() { String contentfolder = ""; Directory curContent = contentDirectory; for(FEntry f : getFileEntries()){ if(f.isInNUSTitle()){ contentfolder = String.format("%08X",tmd.contents[f.getContentID()].ID); if(!contentDirectory.containsFolder(contentfolder)){ Directory newDir = new Directory(contentfolder); contentDirectory.addFolder(newDir); } curContent = contentDirectory.getFolder(contentfolder); Directory current = FSTDirectory; int i = 0; for(String s :f.getPathList()){ i++; //Content if(curContent.containsFolder(s)){ curContent = curContent.getFolder(s); }else{ Directory newDir = new Directory(s); curContent.addFolder(newDir); curContent = newDir; } if(i==f.getPathList().size()){ curContent.addFile(f); } //FST if(current.containsFolder(s)){ current = current.getFolder(s); }else{ Directory newDir = new Directory(s); current.addFolder(newDir); current = newDir; } if(i==f.getPathList().size()){ if(!f.isDir()){ current.addFile(f); } } } } } } //private Map> contentmap = new HashMap<>(); private void parse(byte[] decrypteddata, TitleMetaData tmd) throws IOException { if(!Arrays.equals(Arrays.copyOfRange(decrypteddata, 0, 3), new byte[]{0x46,0x53,0x54})){ throw new IllegalArgumentException("Not a FST. Maybe a wrong key? Don't worry if you only want to download encrypted files!"); } this.totalContentCount = Util.getIntFromBytes(decrypteddata, 8); int base_offset = 0x20+totalContentCount*0x20; this.totalEntries = Util.getIntFromBytes(decrypteddata, base_offset+8); int nameOff = base_offset + totalEntries * 0x10; int level=0; int[] LEntry = new int[16]; int[] Entry = new int[16]; /* for(int i = 0;i()); int my_offset = 0x20 + (i* 0x20); int address = Util.getIntFromBytes(decrypteddata, my_offset+ 0) ; long parentid = Util.getLongFromBytes(decrypteddata, my_offset+ 8) ; int groupid = Util.getIntFromBytes(decrypteddata, my_offset+ 16) ; int size = Util.getIntFromBytes(decrypteddata, my_offset+ 4); byte hashmode = decrypteddata[my_offset+ 20]; System.out.print(String.format("Content %02X: ", i) + " offset " + String.format("%08X", address)+ " size " + String.format("%08X", size) +" "); System.out.print(String.format("parent(?) %016X: ", parentid) + " groupid " + String.format("%08X", groupid)+ " hashmode " + String.format("%01X", hashmode) +" "); System.out.println(String.format("encrypted content size: "+ String.format("%08X", tmd.contents[i].size))); }*/ for(int i = 0;i 0) { while( LEntry[level-1] == i ) { level--; } } int offset = base_offset + i*0x10; //getting the type type = (int) decrypteddata[offset]+128; if((type & FEntry.DIR_FLAG) == 1) dir = true; if((type & FEntry.NOT_IN_NUSTITLE_FLAG) == 0 ) in_nus_title = false; //getting Name decrypteddata[offset] = 0; int nameoff_entry_offset = Util.getIntFromBytes(decrypteddata, offset); int j = 0; int nameoff_entry = nameOff + nameoff_entry_offset; while(decrypteddata[nameoff_entry + j] != 0){j++;} filename = new String(Arrays.copyOfRange(decrypteddata,nameoff_entry, nameoff_entry + j)); //getting offsets. save in two ways offset+=4; fileOffset = Util.getUnsingedIntFromBytes(decrypteddata, offset); offset+=4; fileLength = Util.getUnsingedIntFromBytes(decrypteddata, offset); @SuppressWarnings("unused") int parentOffset = (int) fileOffset; int nextOffset = (int) fileLength; //grabbing flags offset+=4; int flags = Util.getShortFromBytes(decrypteddata, offset); //if((flags & FEntry.EXTRACT_WITH_HASH_FLAG) > 0) extract_withHash = true; if((flags & FEntry.CHANGE_OFFSET_FLAG) == 0) fileOffset <<=5; //grabbing contentid offset+=2; contentID = Util.getShortFromBytes(decrypteddata, offset) ; if((tmd.contents[contentID].type & 0x2003) == 0x2003){ extract_withHash = true; } //remember total size this.totalContentSize += fileLength; if(in_nus_title)this.totalContentSizeInNUS += fileLength; boolean metafolder = false; List pathList = new ArrayList<>(); //getting the full path of entry if(dir) { dirEntries++; Entry[level] = i; LEntry[level++] = nextOffset ; if( level > 15 ) // something is wrong! { break; } /*if(in_nus_title){ System.out.println("Dirname: " + filename); System.out.println("ID: " + i); System.out.println("ParentOffset: " + parentOffset); System.out.println(" NextOffset: " + nextOffset); }*/ }//else{ /* if(in_nus_title){ System.out.println("FILE : " + filename); System.out.println("ID: " + i); }*/ StringBuilder sb = new StringBuilder(); int k = 0; int nameoffoff,nameoff_entrypath; int startlevel = level; if(dir){ startlevel = level -1; } for( j=0; j getMetaFolder() { return metaFolder; } public List getFileEntries() { return fileEntries; } public void setFileEntries(List fileEntries) { this.fileEntries = fileEntries; } public int getTotalContentCount() { return totalContentCount; } public void setTotalContentCount(int totalContentCount) { this.totalContentCount = totalContentCount; } public int getTotalEntries() { return totalEntries; } public void setTotalEntries(int totalEntries) { this.totalEntries = totalEntries; } public int getDirEntries() { return dirEntries; } public void setDirEntries(int dirEntries) { this.dirEntries = dirEntries; } @Override public String toString(){ return "entryCount: " + totalContentCount+ " entries: " + totalEntries; } public int getFileCount() { int i = 0; for(FEntry f: getFileEntries()){ if(!f.isDir()) i++; } return i; } public int getFileCountInNUS() { int i = 0; for(FEntry f: getFileEntries()){ if(!f.isDir() && f.isInNUSTitle()) i++; } return i; } public String notInNUS() { StringBuilder sb = new StringBuilder(); for(FEntry f: getFileEntries()){ if(!f.isDir() && !f.isInNUSTitle()){ sb.append(f.getFullPath() + " " + String.format("%8.2f MB ", f.getFileLength()/1024.0/1024.0) + "\n"); } } return sb.toString(); } public Directory getFSTDirectory() { return FSTDirectory; } public Directory getContentDirectory() { return contentDirectory; } public TitleMetaData getTmd() { return tmd; } public void setTmd(TitleMetaData tmd) { this.tmd = tmd; } public List getFileEntriesByFilePath(String filepath){ List newList = new ArrayList<>(); if(!filepath.startsWith("/")){ filepath = "/" + filepath; } Pattern p = Pattern.compile(filepath); for(FEntry f : fileEntries){ Matcher m = p.matcher(f.getFullPath()); if(m.matches()){ newList.add(f); } } return newList; } }