diff --git a/.gitignore b/.gitignore index 11ddb8b..9391d15 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ Network Trash Folder Temporary Items .apdisk *.class +tickets/* diff --git a/README.md b/README.md index d2a501a..e2bda53 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,41 @@ A port of smea's client (WUPClient.py) for the wupserver. +Open it with +``` +java -jar jwupclient 192.168.x.x +``` + +The following commands are supported (in my ulgy written shell). + +cd: +change directory to parent dir: +cd .. +change directory to relative dir: +cd code +change directory to absolute dir: +cd /vol/storage_mlc01 + +ls: +lists content of the directory + +dl: +downloads a file. +dl filename [targetdir] + +dlfp: +same as dl but keeps the absolute path of the wiiu + +dldir: +downloads a directory + +downloading the current dir +dldir +arguments: +-src (sets the source folder on the wiiu) +-dst (sets the destination folder on your PC) +-fullpath (keeps the absolute path of the wiiu) + +Server and smea's client. https://github.com/smealum/iosuhax/tree/master/wupserver -Everything is from Smea, I'm just porting it! \ No newline at end of file +Everything is from Smea, I'm just porting and extending it! \ No newline at end of file diff --git a/jwupclient.jar b/jwupclient.jar new file mode 100644 index 0000000..3baa4dd Binary files /dev/null and b/jwupclient.jar differ diff --git a/src/de/mas/wupclient/Starter.java b/src/de/mas/wupclient/Starter.java index fbd48b6..861980f 100644 --- a/src/de/mas/wupclient/Starter.java +++ b/src/de/mas/wupclient/Starter.java @@ -1,10 +1,11 @@ package de.mas.wupclient; import java.io.IOException; +import java.util.Scanner; import de.mas.wupclient.client.WUPClient; import de.mas.wupclient.client.operations.DownloadUploadOperations; +import de.mas.wupclient.client.operations.SpecialOperations; import de.mas.wupclient.client.operations.UtilOperations; -import de.mas.wupclient.client.utils.Logger; public class Starter { public static void main(String args[]){ @@ -13,26 +14,114 @@ public class Starter { ip = args[0]; } WUPClient w = new WUPClient(ip); - try { - UtilOperations util = UtilOperations.UtilOperationsFactory(w); - DownloadUploadOperations dlul = DownloadUploadOperations.DownloadUploadOperationsFactory(w); - util.dump_syslog(); - - Logger.logCmd("Lets into the " + w.getCwd() + "/sys/title/00050010/10040200/" + " folder!"); - util.lsRecursive(w.getCwd() + "/sys/title/00050010/10040200/"); - Logger.logCmd("And download the /code/app.xml to /test/app.xml"); - dlul.downloadFile(w.getCwd() + "/sys/title/00050010/10040200/code", "app.xml", "test", null); - Logger.logCmd("done!"); + try { + boolean exit = false; + + System.out.println("JWUPClient. Please enter a command. Enter \"exit\" to exit."); + System.out.println(); + System.out.print(w.getCwd() + " > "); + Scanner reader = new Scanner(System.in); // Reading from System.in + while(!exit){ + + String input = reader.nextLine(); + if(input.equals("exit")){ + exit = true; + break; + } + processCommand(input,w); + System.out.println(); + System.out.print(w.getCwd() + " > "); + } + reader.close(); } catch (IOException e) { e.printStackTrace(); }finally { try { w.FSA_Close(w.get_fsa_handle()); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + } catch (IOException e) { } w.closeSocket(); } } + + private static void processCommand(String input,WUPClient w) throws IOException { + if(input == null || input.isEmpty()){ + return; + } + UtilOperations util = UtilOperations.UtilOperationsFactory(w); + SpecialOperations special = SpecialOperations.SpecialOperationsFactory(w); + DownloadUploadOperations dlul = DownloadUploadOperations.DownloadUploadOperationsFactory(w); + String[] inputs = input.split(" "); + switch(inputs[0]){ + case "ls": + if(inputs.length > 1){ + util.ls(inputs[1]); + }else{ + util.ls(); + } + break; + case "lsr": + util.lsRecursive(); + break; + case "sysdump": + util.dump_syslog(); + break; + case "cd": + if(inputs.length > 1){ + util.cd(inputs[1]); + }else{ + util.cd(); + } + + break; + case "dldir": + String destination = null; + String source = w.getCwd(); + boolean fullpath = false; + if(inputs.length > 1){ + for(int i = 1;i < inputs.length;i++){ + if(inputs[i].equals("-dst")){ + if(inputs.length >= i+1){ + destination = inputs[i+1]; + i++; + } + }else if(inputs[i].equals("-src")){ + if(inputs.length >= i+1){ + source = inputs[i+1]; + i++; + } + }else if(inputs[i].equals("-fullpath")){ + fullpath = true; + } + } + } + dlul.downloadFolder(source,destination,fullpath); + + break; + case "dl": + if(inputs.length == 2){ + dlul.downloadFile("", inputs[1]); + }else if(inputs.length == 3){ + dlul.downloadFile("", inputs[1], inputs[2]); + } + + break; + case "dlfp": //download to full path + if(inputs.length == 2){ + dlul.downloadFile("", inputs[1],w.getCwd()); + }else if(inputs.length == 3){ + dlul.downloadFile("", inputs[1],inputs[2] + "/" + w.getCwd()); + } + + break; + case "nandtickets": //download to full path + special.parseAndDownloadTickets(); + + break; + default: + System.out.println("Command not found!"); + break; + } + + } } diff --git a/src/de/mas/wupclient/client/WUPClient.java b/src/de/mas/wupclient/client/WUPClient.java index e637a6c..1112e73 100644 --- a/src/de/mas/wupclient/client/WUPClient.java +++ b/src/de/mas/wupclient/client/WUPClient.java @@ -13,6 +13,7 @@ import de.mas.wupclient.client.utils.Result; import de.mas.wupclient.client.utils.Utils; public class WUPClient { + public static final int MAX_READ_SIZE = 0x400; private String IP; private int fsaHandle = -1; private String cwd = ""; @@ -33,15 +34,19 @@ public class WUPClient { Logger.logErr("send failed"); e.printStackTrace(); } - ByteBuffer destByteBuffer = ByteBuffer.allocate(0x600); + byte[] result = new byte[0x0600]; int size = getSocket().getInputStream().read(result); - destByteBuffer.put(result, 0, size); - int returnValue = destByteBuffer.getInt(); - return new Result(returnValue,Arrays.copyOfRange(result, 4,result.length)); + ByteBuffer destByteBuffer = ByteBuffer.allocate(0x04); + destByteBuffer.put(Arrays.copyOfRange(result, 0, 4)); + int returnValue = destByteBuffer.getInt(0); + return new Result(returnValue,Arrays.copyOfRange(result, 4,size)); } public byte[] read(int addr, int len) throws IOException{ + if(len > WUPClient.MAX_READ_SIZE){ + throw new IOException("read length > " + WUPClient.MAX_READ_SIZE); + } Result result = send(1, Utils.m_packBE(addr,len)); if(result.getResultValue() == 0){ return result.getData(); @@ -126,7 +131,7 @@ public class WUPClient { e.printStackTrace(); } setSocket(clientSocket); - Logger.log("Connected"); + Logger.log("Connected to " + ip); return clientSocket; } @@ -151,7 +156,7 @@ public class WUPClient { public String getCwd() { return cwd; } - private void setCwd(String cwd) { + public void setCwd(String cwd) { this.cwd = cwd; } public Socket getSocket() { diff --git a/src/de/mas/wupclient/client/operations/DownloadUploadOperations.java b/src/de/mas/wupclient/client/operations/DownloadUploadOperations.java index 3bcb5d9..862d49f 100644 --- a/src/de/mas/wupclient/client/operations/DownloadUploadOperations.java +++ b/src/de/mas/wupclient/client/operations/DownloadUploadOperations.java @@ -32,31 +32,57 @@ public class DownloadUploadOperations extends Operations { setFSA(FSAOperations.FSAOperationsFactory(client)); } - public boolean downloadFolder(String path) throws IOException{ - return downloadFolder(path,null,false); - } - - public boolean downloadFolder(String sourcePath,String targetPath) throws IOException{ - return downloadFolder(sourcePath,targetPath,false); - } - - public boolean downloadFolder(String sourcePath, String targetPath,boolean useRelativPath) throws IOException { - List files = util.ls(sourcePath,true); - if(targetPath == null || targetPath.isEmpty()){ - targetPath = sourcePath; + public boolean downloadFolder(String sourcePath) throws IOException{ + return downloadFolder(sourcePath,null,false); + } + public boolean downloadFolder(String sourcePath, String targetPath,boolean fullpath) throws IOException { + String new_source_path = sourcePath; + + if(!sourcePath.isEmpty() && !sourcePath.startsWith("/")){ + new_source_path = getClient().getCwd() + "/"+ sourcePath; + }else if(sourcePath == null || sourcePath.isEmpty()){ + new_source_path = getClient().getCwd(); } + + if(targetPath == null || targetPath.isEmpty()){ + if(fullpath){ + targetPath = new_source_path; + }else{ + targetPath = ""; + if(!sourcePath.isEmpty() && !sourcePath.startsWith("/")){ + targetPath = sourcePath; + } + } + }else{ + if(fullpath){ + targetPath += new_source_path; + }else{ + if(!sourcePath.isEmpty() && !sourcePath.startsWith("/")){ + targetPath += "/" + sourcePath; + } + } + } + return _downloadFolder(new_source_path,targetPath); + } + private boolean _downloadFolder(String sourcePath, String targetPath) throws IOException { + Logger.logCmd("Downloading folder " + sourcePath); + + List files = util.ls(sourcePath,true); + + Utils.createSubfolder(targetPath); + for(FEntry f: files){ if(f.isFile()){ + //System.out.println(targetPath); downloadFile(sourcePath, f.getFilename(),targetPath); }else{ - downloadFolder(sourcePath + "/" + f.getFilename(), targetPath,useRelativPath); + _downloadFolder(sourcePath + "/" + f.getFilename(), targetPath + "/" + f.getFilename()); } } return false; } - public byte[] downloadFileToByteArray(String path) throws IOException{ - Logger.logCmd("Downloading " + path); + public byte[] downloadFileToByteArray(String path) throws IOException{ int fsa_handle = getClient().get_fsa_handle(); Result res = fsa.FSA_OpenFile(fsa_handle, path, "r"); boolean success = false; @@ -65,38 +91,50 @@ public class DownloadUploadOperations extends Operations { int block_size = 0x400; ByteArrayOutputStream out = new ByteArrayOutputStream(); success = true; + int total_read = 0; while(true){ Result read_result= fsa.FSA_ReadFile(fsa_handle, res.getData(), 0x1, block_size); - + if(read_result.getResultValue() <0){ Logger.logErr("FSA_ReadFile returned " + read_result.getResultValue()); success = false; break; } - + total_read += read_result.getResultValue(); out.write(Arrays.copyOf(read_result.getData(), read_result.getResultValue())); if(read_result.getResultValue() <= 0) break; - } + if((total_read /1024) % 50 == 0){ + System.out.println(String.format("%.3f", (double)(total_read /1024.0)) + " kb done"); + } + } fsa.FSA_CloseFile(fsa_handle, res.getData()); if(success){ + System.out.println("Download done: " + total_read+ " bytes"); result = out.toByteArray(); } } return result; } + + public boolean downloadFile(String sourcePath, String filename) throws IOException { + return downloadFile(sourcePath, filename, ""); + } public boolean downloadFile(String sourcePath, String filename, String targetPath) throws IOException { return downloadFile(sourcePath, filename, targetPath,null); } public boolean downloadFile(String sourcePath,String sourceFilename,String targetPath,String targetFileName) throws IOException { - byte[] data = downloadFileToByteArray(sourcePath + "/" + sourceFilename); - if(data == null){ - System.out.println("failed"); - return false; + + if(sourcePath == null || sourcePath.isEmpty()){ + sourcePath = getClient().getCwd(); } + if(targetPath == null || targetPath.isEmpty()){ + targetPath = ""; + } + String subdir = ""; if(targetFileName == null){ @@ -107,8 +145,17 @@ public class DownloadUploadOperations extends Operations { if(subdir.startsWith("/")){ subdir = subdir.substring(1); } + if(!targetPath.isEmpty()){ + Utils.createSubfolder(subdir); + } + Logger.logCmd("Downloading " + sourcePath + "/" + sourceFilename + " to " + subdir); + + byte[] data = downloadFileToByteArray(sourcePath + "/" + sourceFilename); + if(data == null){ + System.out.println("failed"); + return false; + } - Utils.createSubfolder(subdir); FileOutputStream stream = new FileOutputStream(subdir); try { stream.write(data); @@ -132,5 +179,5 @@ public class DownloadUploadOperations extends Operations { public void setFSA(FSAOperations fsa) { this.fsa = fsa; - } + } } diff --git a/src/de/mas/wupclient/client/operations/FSAOperations.java b/src/de/mas/wupclient/client/operations/FSAOperations.java index 25bfca9..0b1832f 100644 --- a/src/de/mas/wupclient/client/operations/FSAOperations.java +++ b/src/de/mas/wupclient/client/operations/FSAOperations.java @@ -7,6 +7,7 @@ import java.util.Map; import de.mas.wupclient.client.WUPClient; import de.mas.wupclient.client.utils.FEntry; +import de.mas.wupclient.client.utils.FStats; import de.mas.wupclient.client.utils.Logger; import de.mas.wupclient.client.utils.Result; import de.mas.wupclient.client.utils.Utils; @@ -95,6 +96,9 @@ public class FSAOperations extends Operations { } public Result FSA_OpenFile(int handle, String path, String mode) throws IOException{ + if(!path.startsWith("/")){ + path = getClient().getCwd() + "/" + path; + } byte[] inbuffer = new byte[0x520]; Utils.writeNullTerminatedStringToByteArray(inbuffer, path, 0x04); Utils.writeNullTerminatedStringToByteArray(inbuffer, mode, 0x284); @@ -111,6 +115,10 @@ public class FSAOperations extends Operations { } public Result FSA_ReadFile(int handle, int file_handle, int size, int cnt) throws IOException{ + if(size * cnt > WUPClient.MAX_READ_SIZE){ + Logger.logErr("FSA_ReadFile error: size*cnt > " + WUPClient.MAX_READ_SIZE +"(" + (size * cnt) + ")"); + return new Result(-228,new byte[0]); + } byte[] inbuffer = new byte[0x520]; Utils.writeIntToByteArray(inbuffer, size, 0x08); Utils.writeIntToByteArray(inbuffer, cnt, 0x0C); @@ -119,4 +127,22 @@ public class FSAOperations extends Operations { return new Result(result.getResultValue(),result.getData()[0]); } + + public Result FSA_StatFile(int fsa_handle, int handle) throws IOException{ + byte[] inbuffer = new byte[0x520]; + Utils.writeIntToByteArray(inbuffer, handle, 0x04); + + Result result = system.ioctl(fsa_handle, 0x14, inbuffer, 0x293); + FStats stats = null; + if(result.getResultValue() == 0){ + stats = new FStats(result.getData()); + } + System.out.println(result.getResultValue()); + return new Result(result.getResultValue(), stats); + } + + public Result FSA_ReadFilePtr(int fsa_handle, int src_handle, int i, int block_size, int buffer_ptr) { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/de/mas/wupclient/client/operations/FileOperations.java b/src/de/mas/wupclient/client/operations/FileOperations.java new file mode 100644 index 0000000..ce022e5 --- /dev/null +++ b/src/de/mas/wupclient/client/operations/FileOperations.java @@ -0,0 +1,100 @@ +package de.mas.wupclient.client.operations; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import de.mas.wupclient.client.WUPClient; +import de.mas.wupclient.client.utils.Logger; +import de.mas.wupclient.client.utils.Result; + +public class FileOperations extends Operations { + + + private static Map instances = new HashMap<>(); + public static FileOperations FileOperationsFactory(WUPClient client){ + if(!instances.containsKey(client)){ + instances.put(client, new FileOperations(client)); + } + return instances.get(client); + } + + private UtilOperations util = null; + private FSAOperations fsa = null; + private SystemOperations system = null; + + public FileOperations(WUPClient client) { + super(client); + setUtil(UtilOperations.UtilOperationsFactory(client)); + setFsa(FSAOperations.FSAOperationsFactory(client)); + setSystem(SystemOperations.SystemOperationsFactory(client)); + } + public int mkdir(String path, int flags) throws IOException{ + return createDir(path, flags); + } + public int createDir(String path, int flags) throws IOException{ + int fsa_handle = getClient().get_fsa_handle(); + return fsa.FSA_MakeDir(fsa_handle, path, 2); + } + + /* FSA_ReadFilePtr needs to be implemented + public boolean cp(String source, String target) throws IOException{ + return copyFile(source, target); + } + + + public boolean copyFile(String source, String destination) throws IOException{ + int fsa_handle = getClient().get_fsa_handle(); + Result result_src = fsa.FSA_OpenFile(fsa_handle, source, "r"); + if(result_src.getResultValue() != 0){ + Logger.logErr("copyFile error: couldn't open " + source); + return false; + } + Result result_dst = fsa.FSA_OpenFile(fsa_handle, destination, "r"); + if(result_dst.getResultValue() != 0){ + Logger.logErr("copyFile error: couldn't open " + destination); + fsa.FSA_CloseFile(fsa_handle, result_src.getData()); + return false; + } + int block_size = 0x10000; + int buffer_ptr = system.alloc(block_size, 0x40); + int i =0; + int src_handle = result_src.getData(); + int dst_handle = result_dst.getData(); + boolean result = true; + while(true){ + Result result_read = fsa.FSA_ReadFilePtr(fsa_handle, src_handle, 0x1, block_size, buffer_ptr); + if(result_read.getResultValue() < 0){ + Logger.log("copyFile error: reading source file failed."); + result = false; + break; + } + } + system.free(buffer_ptr); + fsa.FSA_CloseFile(fsa_handle, src_handle); + fsa.FSA_CloseFile(fsa_handle, dst_handle); + return result; + }*/ + + public UtilOperations getUtil() { + return util; + } + + public void setUtil(UtilOperations util) { + this.util = util; + } + + public FSAOperations getFsa() { + return fsa; + } + + public void setFsa(FSAOperations fsa) { + this.fsa = fsa; + } + public SystemOperations getSystem() { + return system; + } + public void setSystem(SystemOperations system) { + this.system = system; + } +} diff --git a/src/de/mas/wupclient/client/operations/UtilOperations.java b/src/de/mas/wupclient/client/operations/UtilOperations.java index e1dc694..c60992f 100644 --- a/src/de/mas/wupclient/client/operations/UtilOperations.java +++ b/src/de/mas/wupclient/client/operations/UtilOperations.java @@ -39,11 +39,12 @@ public class UtilOperations extends Operations { return ls(targetPath,false); } public List ls(String targetPath,boolean return_data) throws IOException{ + if(!return_data){ if(targetPath != null){ - Logger.logCmd("ls(" + targetPath + ")"); + //Logger.logCmd("ls(" + targetPath + ")"); }else{ - Logger.logCmd("ls()"); + //Logger.logCmd("ls()"); } } List results = new ArrayList<>(); @@ -51,6 +52,8 @@ public class UtilOperations extends Operations { String path = targetPath; if(targetPath == null || targetPath.isEmpty()){ path = getClient().getCwd(); + }else if(!targetPath.startsWith("/")){ + path = getClient().getCwd() + "/" + targetPath; } Result res = fsa.FSA_OpenDir(fsa_handle, path); @@ -82,6 +85,7 @@ public class UtilOperations extends Operations { if((result = fsa.FSA_CloseDir(fsa_handle, dirhandle)) != 0){ Logger.logErr("CloseDir failed!" + result); } + getClient().FSA_Close(getClient().get_fsa_handle()); return results; } @@ -89,7 +93,7 @@ public class UtilOperations extends Operations { lsRecursive(getClient().getCwd() + "/"); } - public void lsRecursive(String path) throws IOException{ + public void lsRecursive(String path) throws IOException{ List result = ls(path,true); for(FEntry entry : result){ if(entry.isFile()){ @@ -101,6 +105,7 @@ public class UtilOperations extends Operations { } } } + public void dump_syslog() throws IOException { int syslog_address = Utils.bigEndianByteArrayToInt(getClient().read(0x05095ECC, 4)) + 0x10; int block_size = 0x400; @@ -113,12 +118,42 @@ public class UtilOperations extends Operations { break; } } + getClient().FSA_Close(getClient().get_fsa_handle()); } - - public int mkdir(String path, int flags) throws IOException{ + public boolean cd() throws IOException { + return cd(""); + } + public boolean cd(String path) throws IOException { + if(path.equals(".")){ + return true; + } + if(path.equals("..")){ + path = Utils.getParentDir(getClient().getCwd()); + } + if(!path.startsWith("/")&& getClient().getCwd().startsWith("/")){ + return cd(getClient().getCwd() + "/" + path); + } int fsa_handle = getClient().get_fsa_handle(); - return fsa.FSA_MakeDir(fsa_handle, path, 2); - } + String usedPath = path; + if(path.equals("..")){ + usedPath = Utils.getParentDir(getClient().getCwd()); + System.out.println(usedPath +" dd"); + }else if (path.isEmpty()){ + usedPath = getClient().getCwd(); + } + boolean final_result = false; + Result result = fsa.FSA_OpenDir(fsa_handle,usedPath); + if(result.getResultValue() == 0){ + getClient().setCwd(usedPath); + fsa.FSA_CloseDir(fsa_handle, result.getData()); + final_result = true; + }else{ + Logger.logErr("path does not exists"); + final_result = false; + } + getClient().FSA_Close(getClient().get_fsa_handle()); + return final_result; + } public int mount(String device_path, String volume_path, int flags) throws IOException{ int fsa_handle = getClient().get_fsa_handle(); diff --git a/src/de/mas/wupclient/client/utils/FStats.java b/src/de/mas/wupclient/client/utils/FStats.java new file mode 100644 index 0000000..9d7ccdd --- /dev/null +++ b/src/de/mas/wupclient/client/utils/FStats.java @@ -0,0 +1,39 @@ +package de.mas.wupclient.client.utils; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +public class FStats { + int[] unk1 = new int[0x4]; + int size; // size in bytes + int physsize; // physical size on disk in bytes + int[] unk2 = new int[0x13]; + + public FStats(byte[] data) { + if(data == null || data.length < 0x64){ + return; + } + ByteBuffer buffer = ByteBuffer.allocate(0x64); + buffer.put(Arrays.copyOfRange(data, 0x00, 0x64)); + int offset = 0x00; + for(int i = 0;i>>" + log); + log("" + log); } public static void logErr(String log){ System.err.println("> Error: " + log); diff --git a/src/de/mas/wupclient/client/utils/Utils.java b/src/de/mas/wupclient/client/utils/Utils.java index 758ed91..5d57ec8 100644 --- a/src/de/mas/wupclient/client/utils/Utils.java +++ b/src/de/mas/wupclient/client/utils/Utils.java @@ -44,7 +44,7 @@ public class Utils { public static String getStringFromByteArray(byte[] data){ int i = 0; - while(data[i] != 0 && (i) < data.length){ + while(data[i] != 0 && (i) < data.length-1){ i++; } String string = ""; @@ -145,8 +145,23 @@ public class Utils { return null; } - public static void createSubfolder(String folder){ + public static String getParentDir(String path){ + if(path == null || path.isEmpty() || path.equals("/")){ + return "/"; + } + String new_path = ""; + String [] pathpath = path.split("/"); + for(int i = 0;i