diff --git a/src/de/mas/wupclient/Starter.java b/src/de/mas/wupclient/Starter.java index 38b3651..5fcc4fa 100644 --- a/src/de/mas/wupclient/Starter.java +++ b/src/de/mas/wupclient/Starter.java @@ -1,39 +1,34 @@ package de.mas.wupclient; import java.io.IOException; -import java.util.List; import de.mas.wupclient.client.WUPClient; -import de.mas.wupclient.client.operations.FSAOperations; import de.mas.wupclient.client.operations.UtilOperations; -import de.mas.wupclient.client.utils.FEntry; import de.mas.wupclient.client.utils.Logger; public class Starter { - public static void main(String args[]){ - WUPClient w = new WUPClient("192.168.0.035"); + public static void main(String args[]){ + String ip = "192.168.0.35"; + if(args.length > 0){ + ip = args[0]; + } + WUPClient w = new WUPClient(ip); try { UtilOperations util = UtilOperations.UtilOperationsFactory(w); - FSAOperations fsa = FSAOperations.FSAOperationsFactory(w); - util.ls(); - //List result = util.ls("/vol/storage_mlc01/",true); - printLSRecursive("/vol/storage_mlc01/sys/title/00050010/1004c200/",util); - w.FSA_Close(w.get_fsa_handle()); - w.closeSocket(); + 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"); + util.downloadFile(w.getCwd() + "/sys/title/00050010/10040200/code", "app.xml", "test", null); + Logger.logCmd("done!"); } catch (IOException e) { e.printStackTrace(); - } - } - - public static void printLSRecursive(String path,UtilOperations util) throws IOException{ - List result = util.ls(path,true); - for(FEntry entry : result){ - if(entry.isFile()){ - Logger.log(path + entry.getFilename()); - }else{ - String newPath = path + entry.getFilename() + "/"; - Logger.log(newPath); - printLSRecursive(newPath, util); + }finally { + try { + w.FSA_Close(w.get_fsa_handle()); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } + w.closeSocket(); } } } diff --git a/src/de/mas/wupclient/client/operations/FSAOperations.java b/src/de/mas/wupclient/client/operations/FSAOperations.java index f041f61..8ac3ef8 100644 --- a/src/de/mas/wupclient/client/operations/FSAOperations.java +++ b/src/de/mas/wupclient/client/operations/FSAOperations.java @@ -1,14 +1,13 @@ package de.mas.wupclient.client.operations; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import de.mas.wupclient.client.WUPClient; import de.mas.wupclient.client.utils.FEntry; +import de.mas.wupclient.client.utils.Logger; import de.mas.wupclient.client.utils.Result; import de.mas.wupclient.client.utils.Utils; @@ -23,7 +22,7 @@ public class FSAOperations extends Operations { private IoctlOperations ioctl = null; - public FSAOperations(WUPClient client) { + private FSAOperations(WUPClient client) { super(client); setIoctlOperations(IoctlOperations.IoctlOperationsFactory(client)); } @@ -42,11 +41,7 @@ public class FSAOperations extends Operations { Result res = ioctl.ioctl(handle, 0x0A, inbuffer, 0x293); - ByteBuffer destByteBuffer = ByteBuffer.allocate(0x04); - destByteBuffer.order(ByteOrder.BIG_ENDIAN); - destByteBuffer.put(Arrays.copyOfRange(res.getData(), 0x04, 0x08)); - - return new Result(res.getResultValue(),destByteBuffer.getInt(0x00)); + return new Result(res.getResultValue(),Utils.bigEndianByteArrayToInt(Arrays.copyOfRange(res.getData(), 0x04, 0x08))); } public int FSA_CloseDir(int handle, int dirhandle) throws IOException{ @@ -61,14 +56,67 @@ public class FSAOperations extends Operations { Utils.writeIntToByteArray(inbuffer,dirhandle,4); Result res = ioctl.ioctl(fsa_handle, 0x0B, inbuffer, 0x293); - + byte[] unknowndata = Arrays.copyOfRange(res.getData(), 0x04, 0x68); - String filename = Utils.getStringFromByteArray(Arrays.copyOfRange(res.getData(), 0x68, res.getData().length)); if(res.getResultValue() == 0){ - return new Result(res.getResultValue(),new FEntry(filename,((unknowndata[0] & 0x01) == 0x01),unknowndata)); + return new Result(res.getResultValue(),new FEntry(filename,((char)unknowndata[0] & 128) != 128,unknowndata)); }else{ return new Result(res.getResultValue(),null); } } + + public int FSA_Mount(int handle, String device_path, String volume_path, int flags) throws IOException{ + Logger.logCmd("Mounting " + device_path + " to " + volume_path); + byte[] inbuffer = new byte[0x520]; + Utils.writeNullTerminatedStringToByteArray(inbuffer, device_path, 0x0004); + Utils.writeNullTerminatedStringToByteArray(inbuffer, volume_path, 0x0284); + Utils.writeIntToByteArray(inbuffer,flags,0x0504); + + Result result = ioctl.ioctlv(handle, 0x01, new byte[][] {inbuffer,new byte[0]}, new int[]{0x293}); + return result.getResultValue(); + } + + public int FSA_Unmount(int handle, String volume_path, int flags) throws IOException{ + Logger.logCmd("Unmounting " + volume_path); + byte[] inbuffer = new byte[0x520]; + Utils.writeNullTerminatedStringToByteArray(inbuffer, volume_path, 0x04); + Utils.writeIntToByteArray(inbuffer,flags,0x284); + Result result = ioctl.ioctl(handle,0x02,inbuffer,0x293); + return result.getResultValue(); + } + + public int FSA_MakeDir(int handle, String path, int flags) throws IOException{ + byte[] inbuffer = new byte[0x520]; + Utils.writeNullTerminatedStringToByteArray(inbuffer, path, 0x04); + Utils.writeIntToByteArray(inbuffer,flags,0x284); + Result result = ioctl.ioctl(handle, 0x07, inbuffer, 0x293); + return result.getResultValue(); + } + + public Result FSA_OpenFile(int handle, String path, String mode) throws IOException{ + byte[] inbuffer = new byte[0x520]; + Utils.writeNullTerminatedStringToByteArray(inbuffer, path, 0x04); + Utils.writeNullTerminatedStringToByteArray(inbuffer, mode, 0x284); + Result result = ioctl.ioctl(handle, 0x0E, inbuffer, 0x293); + + return new Result(result.getResultValue(),Utils.bigEndianByteArrayToInt(Arrays.copyOfRange(result.getData(), 0x04, 0x08))); + } + + public int FSA_CloseFile(int handle, int file_handle) throws IOException{ + byte[] inbuffer = new byte[0x520]; + Utils.writeIntToByteArray(inbuffer, file_handle, 0x04); + Result res = ioctl.ioctl(handle, 0x15, inbuffer, 0x293); + return res.getResultValue(); + } + + public Result FSA_ReadFile(int handle, int file_handle, int size, int cnt) throws IOException{ + byte[] inbuffer = new byte[0x520]; + Utils.writeIntToByteArray(inbuffer, size, 0x08); + Utils.writeIntToByteArray(inbuffer, cnt, 0x0C); + Utils.writeIntToByteArray(inbuffer, file_handle, 0x14); + Result result = ioctl.ioctlv(handle, 0x0F, new byte[][] {inbuffer}, new int[]{size * cnt,0x293}); + + return new Result(result.getResultValue(),result.getData()[0]); + } } diff --git a/src/de/mas/wupclient/client/operations/IoctlOperations.java b/src/de/mas/wupclient/client/operations/IoctlOperations.java index 4c5e758..d6ed09c 100644 --- a/src/de/mas/wupclient/client/operations/IoctlOperations.java +++ b/src/de/mas/wupclient/client/operations/IoctlOperations.java @@ -89,4 +89,8 @@ public class IoctlOperations extends Operations { getClient().free(iovecs); return new Result(ret,out_data); } + + public Result ioctlv(int handle, int cmd, byte[][] inbufs, int[] outbuf_sizes) throws IOException { + return ioctlv(handle,cmd,inbufs,outbuf_sizes,new int[0][],new int[0][]); + } } diff --git a/src/de/mas/wupclient/client/operations/LoadMetaFailedException.java b/src/de/mas/wupclient/client/operations/LoadMetaFailedException.java new file mode 100644 index 0000000..2f2fc75 --- /dev/null +++ b/src/de/mas/wupclient/client/operations/LoadMetaFailedException.java @@ -0,0 +1,10 @@ +package de.mas.wupclient.client.operations; + +public class LoadMetaFailedException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + +} diff --git a/src/de/mas/wupclient/client/operations/MountingFailedException.java b/src/de/mas/wupclient/client/operations/MountingFailedException.java new file mode 100644 index 0000000..59f0f6d --- /dev/null +++ b/src/de/mas/wupclient/client/operations/MountingFailedException.java @@ -0,0 +1,10 @@ +package de.mas.wupclient.client.operations; + +public class MountingFailedException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + +} diff --git a/src/de/mas/wupclient/client/operations/UtilOperations.java b/src/de/mas/wupclient/client/operations/UtilOperations.java index ffa5567..0427501 100644 --- a/src/de/mas/wupclient/client/operations/UtilOperations.java +++ b/src/de/mas/wupclient/client/operations/UtilOperations.java @@ -1,7 +1,10 @@ package de.mas.wupclient.client.operations; +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -10,6 +13,7 @@ import de.mas.wupclient.client.WUPClient; import de.mas.wupclient.client.utils.FEntry; import de.mas.wupclient.client.utils.Logger; import de.mas.wupclient.client.utils.Result; +import de.mas.wupclient.client.utils.Utils; public class UtilOperations extends Operations { private static Map instances = new HashMap<>(); @@ -22,7 +26,7 @@ public class UtilOperations extends Operations { private FSAOperations fsa = null; - public UtilOperations(WUPClient client) { + private UtilOperations(WUPClient client) { super(client); setFSAOperations(FSAOperations.FSAOperationsFactory(client)); } @@ -80,7 +84,149 @@ public class UtilOperations extends Operations { Logger.logErr("CloseDir failed!" + result); } return results; - } + } + + public void lsRecursive() throws IOException { + lsRecursive(getClient().getCwd() + "/"); + } + + public void lsRecursive(String path) throws IOException{ + List result = ls(path,true); + for(FEntry entry : result){ + if(entry.isFile()){ + Logger.log(path + entry.getFilename()); + }else{ + String newPath = path + entry.getFilename() + "/"; + Logger.log(newPath); + lsRecursive(newPath); + } + } + } + + public int mount(String device_path, String volume_path, int flags) throws IOException{ + int fsa_handle = getClient().get_fsa_handle(); + return fsa.FSA_Mount(fsa_handle, device_path, volume_path, 2); + } + public int unmount(String volume_path, int flags) throws IOException{ + int fsa_handle = getClient().get_fsa_handle(); + return fsa.FSA_Unmount(fsa_handle, volume_path, 2); + } + + public int mount_sdcard() throws IOException{ + return mount("/dev/sdcard01", "/vol/storage_sdcard", 2); + } + + public int unmount_sdcard() throws IOException{ + return unmount("/vol/storage_sdcard", 2); + } + + public int mount_odd_content() throws IOException{ + return mount("/dev/odd03", "/vol/storage_odd_content", 2); + } + + public int unmount_odd_content() throws IOException{ + return unmount("/vol/storage_odd_content", 2); + } + + public int mount_odd_ticket() throws IOException{ + return mount("/dev/odd01", "/vol/storage_odd_ticket", 2); + } + + public int unmount_odd_ticket() throws IOException{ + return unmount("/vol/storage_odd_ticket", 2); + } + + public int mkdir(String path, int flags) throws IOException{ + int fsa_handle = getClient().get_fsa_handle(); + return fsa.FSA_MakeDir(fsa_handle, path, 2); + } + + 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 = ls(sourcePath,true); + if(targetPath == null || targetPath.isEmpty()){ + targetPath = sourcePath; + } + for(FEntry f: files){ + if(f.isFile()){ + downloadFile(sourcePath, f.getFilename(),targetPath); + }else{ + downloadFolder(sourcePath + "/" + f.getFilename(), targetPath,useRelativPath); + } + } + return false; + } + + public byte[] downloadFileToByteArray(String path) throws IOException{ + Logger.logCmd("Downloading " + path); + int fsa_handle = getClient().get_fsa_handle(); + Result res = fsa.FSA_OpenFile(fsa_handle, path, "r"); + boolean success = false; + byte[] result = null; + if(res.getResultValue() == 0){ + int block_size = 0x400; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + success = true; + 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; + } + + out.write(Arrays.copyOf(read_result.getData(), read_result.getResultValue())); + + if(read_result.getResultValue() <= 0) + break; + } + fsa.FSA_CloseFile(fsa_handle, res.getData()); + if(success){ + result = out.toByteArray(); + } + } + return result; + } + + 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; + } + String subdir = ""; + + if(targetFileName == null){ + subdir += targetPath + "/" + sourceFilename; + }else{ + subdir += targetPath + "/" + targetFileName; + } + if(subdir.startsWith("/")){ + subdir = subdir.substring(1); + } + //System.out.println(subdir); + Utils.createSubfolder(subdir); + FileOutputStream stream = new FileOutputStream(subdir); + try { + stream.write(data); + } finally { + stream.close(); + } + return true; + } + public FSAOperations getFSAOperations() { return fsa; } diff --git a/src/de/mas/wupclient/client/utils/Logger.java b/src/de/mas/wupclient/client/utils/Logger.java index d9add65..1682582 100644 --- a/src/de/mas/wupclient/client/utils/Logger.java +++ b/src/de/mas/wupclient/client/utils/Logger.java @@ -2,10 +2,10 @@ package de.mas.wupclient.client.utils; public class Logger { public static void log(String log){ - System.out.println("> " + log); + System.out.println(log); } public static void logCmd(String log){ - log("Executing: " + log); + log(">>>" + log); } public static void logErr(String log){ System.err.println("> Error: " + log); diff --git a/src/de/mas/wupclient/client/utils/MetaInformation.java b/src/de/mas/wupclient/client/utils/MetaInformation.java new file mode 100644 index 0000000..e009828 --- /dev/null +++ b/src/de/mas/wupclient/client/utils/MetaInformation.java @@ -0,0 +1,137 @@ +package de.mas.wupclient.client.utils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class MetaInformation implements Comparable, Serializable{ + private static final long serialVersionUID = 1L; + + private long titleID; + private String longnameEN; + private String ID6; + private String product_code; + private String content_platform; + private String company_code; + private int region; + + public enum Region{ + EUR, + USA, + JAP, + UKWN + } + + public MetaInformation(long titleID, String longnameEN, String ID6, String product_code,String content_platform,String company_code,int region) { + setTitleID(titleID); + setLongnameEN(longnameEN); + setID6(ID6); + setProduct_code(product_code); + setCompany_code(company_code); + setContent_platform(content_platform); + setRegion(region); + } + + public MetaInformation() { + + } + + public Region getRegionAsRegion() { + switch (region) { + case 1: return Region.JAP; + case 2: return Region.USA; + case 4: return Region.EUR; + default: return Region.UKWN; + } + } + + public String getContent_platform() { + return content_platform; + } + + public void setContent_platform(String content_platform) { + this.content_platform = content_platform; + } + + public String getCompany_code() { + return company_code; + } + + public void setCompany_code(String company_code) { + this.company_code = company_code; + } + + public String getProduct_code() { + return product_code; + } + + public void setProduct_code(String product_code) { + this.product_code = product_code; + } + + public long getTitleID() { + return titleID; + } + + public void setTitleID(long titleID) { + this.titleID = titleID; + } + + public String getLongnameEN() { + return longnameEN; + } + + public void setLongnameEN(String longnameEN) { + this.longnameEN = longnameEN; + } + + public String getID6() { + return ID6; + } + + public void setID6(String iD6) { + ID6 = iD6; + } + + public int getRegion() { + return region; + } + + public void setRegion(int region) { + this.region = region; + } + + public String getTitleIDAsString() { + return String.format("%08X-%08X", titleID>>32,titleID<<32>>32); + + } + + @Override + public String toString(){ + String result = getTitleIDAsString() + ";" + region +";" + getContent_platform() + ";" + getCompany_code() + ";"+ getProduct_code()+ ";" + getID6() + ";" + getLongnameEN(); + for(Integer i :versions){ + result += ";" + i; + } + return result; + } + + @Override + public int compareTo(MetaInformation o) { + return getLongnameEN().compareTo(o.getLongnameEN()); + } + + public void init(MetaInformation n) { + setTitleID(n.getTitleID()); + setRegion(n.region); + setCompany_code(n.company_code); + setContent_platform(n.content_platform); + setID6(n.ID6); + setLongnameEN(n.longnameEN); + setProduct_code(n.product_code); + } + + @Override + public boolean equals(Object o){ + return titleID == ((MetaInformation)o).titleID; + } +} diff --git a/src/de/mas/wupclient/client/utils/Utils.java b/src/de/mas/wupclient/client/utils/Utils.java index 91daf86..f08d706 100644 --- a/src/de/mas/wupclient/client/utils/Utils.java +++ b/src/de/mas/wupclient/client/utils/Utils.java @@ -1,10 +1,21 @@ package de.mas.wupclient.client.utils; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.io.UnsupportedEncodingException; +import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + public class Utils { public static String ByteArrayToString(byte[] ba) { @@ -59,6 +70,20 @@ public class Utils { destByteBuffer.putInt(value); return destByteBuffer.array(); } + + public static int bigEndianByteArrayToInt(byte[] data){ + return bigEndianByteArrayToInt(data,0); + } + public static int bigEndianByteArrayToInt(byte[] data,int offset){ + if(data == null){ + Logger.logErr("bigEndianByteArrayToInt failed. data is null"); + return 0; + } + ByteBuffer destByteBuffer = ByteBuffer.allocate(data.length); + destByteBuffer.order(ByteOrder.BIG_ENDIAN); + destByteBuffer.put(data); + return destByteBuffer.getInt(offset); + } public static boolean writeNullTerminatedStringToByteArray(byte[] target, String input, int offset) { if(writeStringToByteArray(target, input, offset)){ @@ -84,4 +109,65 @@ public class Utils { return false; } + public static long StringToLong(String s) { + try{ + BigInteger bi = new BigInteger(s, 16); + return bi.longValue(); + }catch(NumberFormatException e){ + System.err.println("Invalid Title ID"); + return 0L; + } + } + + public static MetaInformation readMeta(InputStream bis) { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder; + + String ID6 = null; + try { + builder = factory.newDocumentBuilder(); + Document document = builder.parse(bis); + String proc = document.getElementsByTagName("product_code").item(0).getTextContent().toString(); + String comp = document.getElementsByTagName("company_code").item(0).getTextContent().toString(); + String title_id = document.getElementsByTagName("title_id").item(0).getTextContent().toString(); + + String longname = document.getElementsByTagName("longname_en").item(0).getTextContent().toString(); + longname = longname.replace("\n", " "); + String id = proc.substring(proc.length()-4, proc.length()); + comp = comp.substring(comp.length()-2, comp.length()); + ID6 = id+comp; + String company_code = document.getElementsByTagName("company_code").item(0).getTextContent().toString(); + String content_platform = document.getElementsByTagName("content_platform").item(0).getTextContent().toString(); + String region = document.getElementsByTagName("region").item(0).getTextContent().toString(); + MetaInformation nusinfo = new MetaInformation(Utils.StringToLong(title_id),longname,ID6,proc,content_platform,company_code,(int) StringToLong(region),new String[1]); + return nusinfo; + + } catch (ParserConfigurationException | SAXException | IOException e) { + Logger.log("Error while parsing the meta files"); + } + return null; + } + + public static void createSubfolder(String folder){ + + String [] path = folder.split("/"); + File folder_ = null; + String foldername = new String(); + if(path.length == 1){ + folder_ = new File(folder); + if(!folder_.exists()){ + folder_.mkdir(); + } + } + for(int i = 0;i