From a29b62057944d5090499b2a861165f5a0c36c0c5 Mon Sep 17 00:00:00 2001 From: sirjonasxx <36828922+sirjonasxx@users.noreply.github.com> Date: Sun, 27 Dec 2020 21:32:40 +0100 Subject: [PATCH] unity local fileserver --- .../g_chrome_tools/UnityFileServer.java | 4 - .../g_chrome_tools/UnitywebModifyer.java | 4 - .../unity_tools/GUnityFileServer.java | 150 +++++++++++++++ .../unity_tools/UnityWebModifyer.java | 177 ++++++++++++++++++ 4 files changed, 327 insertions(+), 8 deletions(-) delete mode 100644 G-Earth/src/main/java/gearth/services/g_chrome_tools/UnityFileServer.java delete mode 100644 G-Earth/src/main/java/gearth/services/g_chrome_tools/UnitywebModifyer.java create mode 100644 G-Earth/src/main/java/gearth/services/unity_tools/GUnityFileServer.java create mode 100644 G-Earth/src/main/java/gearth/services/unity_tools/UnityWebModifyer.java diff --git a/G-Earth/src/main/java/gearth/services/g_chrome_tools/UnityFileServer.java b/G-Earth/src/main/java/gearth/services/g_chrome_tools/UnityFileServer.java deleted file mode 100644 index 932ed89..0000000 --- a/G-Earth/src/main/java/gearth/services/g_chrome_tools/UnityFileServer.java +++ /dev/null @@ -1,4 +0,0 @@ -package gearth.services.g_chrome_tools; - -public class UnityFileServer { -} diff --git a/G-Earth/src/main/java/gearth/services/g_chrome_tools/UnitywebModifyer.java b/G-Earth/src/main/java/gearth/services/g_chrome_tools/UnitywebModifyer.java deleted file mode 100644 index 20ef3e8..0000000 --- a/G-Earth/src/main/java/gearth/services/g_chrome_tools/UnitywebModifyer.java +++ /dev/null @@ -1,4 +0,0 @@ -package gearth.services.g_chrome_tools; - -public class UnitywebModifyer { -} diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/GUnityFileServer.java b/G-Earth/src/main/java/gearth/services/unity_tools/GUnityFileServer.java new file mode 100644 index 0000000..50cab54 --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/unity_tools/GUnityFileServer.java @@ -0,0 +1,150 @@ +package gearth.services.unity_tools; + +import gearth.Main; +import gearth.misc.Cacher; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Random; + +public class GUnityFileServer extends HttpServlet +{ + + public final static int FILESERVER_PORT = 9089; + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException + { + try { + response.addHeader("Access-Control-Allow-Origin", "*"); + String path = request.getPathInfo(); + + if (path.equals("/ping")) { + response.setStatus(200); + return; + } + + String url = request.getParameter("blabla"); + if (url == null || url.isEmpty()) { + response.setStatus(404); + return; + } + + response.addHeader("ETag", createETag()); + + String revision = url.split("/")[4]; + + if (path.equals("/prod")) getProd(revision, response); + else if (path.equals("/data")) getData(revision, response); + else if (path.equals("/wasm/code")) getWasmCode(revision, response); + else if (path.equals("/wasm/framework")) getWasmFramework(revision, response); + else if (path.equals("/unityloader")) getUnityLoader(revision, response); + else if (path.equals("/version")) getVersion(revision, response, url); + else if (path.equals("/logo")) getLogo(response); + else { + response.setStatus(404); + } + + response.setStatus(200); + } + catch (Exception e) { + e.printStackTrace(); + response.setStatus(500); + } + + } + + private static String getRandomHexString(int numchars){ + Random r = new Random(); + StringBuffer sb = new StringBuffer(); + while(sb.length() < numchars){ + sb.append(Integer.toHexString(r.nextInt())); + } + return sb.toString().substring(0, numchars); + } + + private String createETag() { + return "W/\"" + getRandomHexString(6) + "-" + getRandomHexString(13) + "\""; + } + + private String getDir(String revision) { + return Cacher.getCacheDir() + File.separator + "UNITY-" + revision + File.separator; + } + + + private void fileResponse(String file, HttpServletResponse response, String contentType) throws IOException { + ServletOutputStream out = response.getOutputStream(); + InputStream in = new FileInputStream(file); +// response.setContentType(contentType); + + byte[] bytes = new byte[4096]; + int bytesRead; + + while ((bytesRead = in.read(bytes)) != -1) { + out.write(bytes, 0, bytesRead); + } + + in.close(); + out.close(); + } + + + private void getProd(String revision, HttpServletResponse response) throws IOException { + UnityWebModifyer unitywebModifyer = new UnityWebModifyer(revision, getDir(revision)); + unitywebModifyer.modifyAllFiles(); + + fileResponse(getDir(revision) + UnityWebModifyer.UNITY_PROD, response, "application/json"); + } + + private void getData(String revision, HttpServletResponse response) throws IOException { + // application/vnd.unity + fileResponse(getDir(revision) + UnityWebModifyer.UNITY_DATA, response, "application/vnd.unity"); + } + + private void getWasmCode(String revision, HttpServletResponse response) throws IOException { + fileResponse(getDir(revision) + UnityWebModifyer.UNITY_CODE, response, "application/vnd.unity"); + } + + private void getWasmFramework(String revision, HttpServletResponse response) throws IOException { + fileResponse(getDir(revision) + UnityWebModifyer.UNITY_FRAMEWORK, response, "application/vnd.unity"); + } + + private void getUnityLoader(String revision, HttpServletResponse response) throws IOException { + UnityWebModifyer unitywebModifyer = new UnityWebModifyer(revision, getDir(revision)); + unitywebModifyer.modifyAllFiles(); + + fileResponse(getDir(revision) + UnityWebModifyer.UNITY_LOADER, response, "text/javascript"); + } + + private void getVersion(String revision, HttpServletResponse response, String url) throws IOException { + BufferedReader in = new BufferedReader(new InputStreamReader(new URL(url).openStream())); + + String version = in.readLine(); + String realVersion = version.split(" ")[0]; + + response.getOutputStream().print(realVersion + " - G-Earth by sirjonasxx"); + response.getOutputStream().close(); + } + + private void getLogo(HttpServletResponse response) throws IOException { + OutputStream out = response.getOutputStream(); + InputStream in = Main.class.getResourceAsStream("G-EarthLogo.png"); + + byte[] bytes = new byte[4096]; + int bytesRead; + + while ((bytesRead = in.read(bytes)) != -1) { + out.write(bytes, 0, bytesRead); + } + + in.close(); + out.close(); + } + +} + diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/UnityWebModifyer.java b/G-Earth/src/main/java/gearth/services/unity_tools/UnityWebModifyer.java new file mode 100644 index 0000000..a950657 --- /dev/null +++ b/G-Earth/src/main/java/gearth/services/unity_tools/UnityWebModifyer.java @@ -0,0 +1,177 @@ +package gearth.services.unity_tools; + +import wasm.disassembly.InvalidOpCodeException; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class UnityWebModifyer { + + public final static String UNITY_PROD = "habbo2020-global-prod.json"; + public final static String UNITY_DATA = "habbo2020-global-prod.data.unityweb"; + public final static String UNITY_CODE = "habbo2020-global-prod.wasm.code.unityweb"; + public final static String UNITY_FRAMEWORK = "habbo2020-global-prod.wasm.framework.unityweb"; + public final static String UNITY_LOADER = "UnityLoader.js"; + + private final static String UNITYFILES_URL = "https://images.habbo.com/habbo-webgl-clients/{revision}/WebGL/habbo2020-global-prod/Build/"; + + private final String revision; + private final File saveFolder; + private final String currentUrl; + + + public UnityWebModifyer(String revision, String saveFolder) { + this.revision = revision; + this.currentUrl = UNITYFILES_URL.replace("{revision}", revision); + this.saveFolder = new File(saveFolder); + } + + public boolean modifyAllFiles() { + if (saveFolder.exists()) { + return true; + } + saveFolder.mkdirs(); + + try { + modifyProdFile(); + modifyDataFile(); + modifyCodeFile(); + modifyFrameworkFile(); + modifyUnityLoader(); + + } catch (Exception e) { + e.printStackTrace(); + saveFolder.delete(); + return false; + } + return true; + } + + // return urls for: data, code & framework file + private void modifyProdFile() throws IOException { + String prodUrl = currentUrl + UNITY_PROD; + + URLConnection connection = new URL(prodUrl).openConnection(); + InputStream is = connection.getInputStream(); + BufferedReader in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + + FileWriter fileWriter = new FileWriter(new File(saveFolder, UNITY_PROD)); + BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); + + String line; + while ((line = in.readLine()) != null) { + bufferedWriter.write(line); + bufferedWriter.newLine(); + } + + bufferedWriter.close(); + in.close(); + } + + private void downloadToFile(URL url, File file) throws IOException { + BufferedInputStream in = new BufferedInputStream(url.openStream()); + FileOutputStream fileOutputStream = new FileOutputStream(file); + byte[] dataBuffer = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead); + } + fileOutputStream.close(); + in.close(); + } + + private void modifyDataFile() throws IOException { + File dataFile = new File(saveFolder, UNITY_DATA); + URL dataUrl = new URL(currentUrl + UNITY_DATA); + downloadToFile(dataUrl, dataFile); + } + + private void modifyCodeFile() throws IOException, InvalidOpCodeException { + File codeFile = new File(saveFolder, UNITY_CODE); + URL codeUrl = new URL(currentUrl + UNITY_CODE); + downloadToFile(codeUrl, codeFile); + + WasmCodePatcher patcher = new WasmCodePatcher(codeFile.getAbsolutePath()); + patcher.patch(); + } + + + private String insertFrameworkCode(String fileContents, int index, String codeName) throws IOException { + BufferedReader code = new BufferedReader(new InputStreamReader(UnityWebModifyer.class.getResourceAsStream(codeName), StandardCharsets.UTF_8)); + + String firstPart = fileContents.substring(0, index); + String lastPart = fileContents.substring(index); + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(firstPart); + + stringBuilder.append("\n"); + String line; + while ((line = code.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + + stringBuilder.append(lastPart); + return stringBuilder.toString(); + } + + private void modifyFrameworkFile() throws IOException { + File frameworkFile = new File(saveFolder, UNITY_FRAMEWORK); + URL frameworkUrl = new URL(currentUrl + UNITY_FRAMEWORK); + downloadToFile(frameworkUrl, frameworkFile); + + + byte[] encoded = Files.readAllBytes(Paths.get(frameworkFile.getAbsolutePath())); + String contents = new String(encoded, StandardCharsets.UTF_8); + + contents = insertFrameworkCode(contents, 0, "js_code/unity_code.js"); + + String exportSearch = "Module.asmLibraryArg,buffer);Module[\"asm\"]=asm;"; + int exportIndex = contents.indexOf(exportSearch) + exportSearch.length(); + contents = insertFrameworkCode(contents, exportIndex, "js_code/unity_exports.js"); + + String importSearch = "if(!env[\"tableBase\"]){env[\"tableBase\"]=0}"; + int importIndex = contents.indexOf(importSearch) + importSearch.length(); + contents = insertFrameworkCode(contents, importIndex, "js_code/unity_imports.js"); + + contents = contents + .replace("var _free", "_free") + .replace("var _malloc", "_malloc") + .replace("{{RevisionName}}", revision); + + BufferedWriter writer = new BufferedWriter(new FileWriter(frameworkFile)); + writer.write(contents); + writer.close(); + } + + private void modifyUnityLoader() throws IOException { + File loaderFile = new File(saveFolder, UNITY_LOADER); + URL loaderUrl = new URL(currentUrl + UNITY_LOADER); + downloadToFile(loaderUrl, loaderFile); + + byte[] encoded = Files.readAllBytes(Paths.get(loaderFile.getAbsolutePath())); + String contents = new String(encoded, StandardCharsets.UTF_8); + + contents = contents.replace("o.result.responseHeaders[e]==a.getResponseHeader(e)", "false"); + contents = contents.replace("i.responseHeaders[e]=o.getResponseHeader(e)", + "const genRanHex = size => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');\n" + + " if (e === \"ETag\") {\n" + + " i.responseHeaders[e] = \"W/\\\"\" + genRanHex(6) + \"-\" + genRanHex(13) + \"\\\"\"\n" + + " }\n" + + " else {\n" + + " i.responseHeaders[e] = o.getResponseHeader(e)\n" + + " }"); + + BufferedWriter writer = new BufferedWriter(new FileWriter(loaderFile)); + writer.write(contents); + writer.close(); + } + + +}