diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 7843a0a..de88e1b 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -2,7 +2,7 @@ "env": { "includePath": [ "${default}", - "${env:EMSDK}/upstream/emscripten/system/include", + "${env:EMSDK}/upstream/emscripten/cache/sysroot/include", "${workspaceFolder}/wasm/deps/zlib", "${workspaceFolder}/wasm/deps/openssl/include" ], @@ -58,6 +58,24 @@ "cStandard": "c11", "cppStandard": "c++11", "intelliSenseMode": "${default}" + }, + { + "name": "Emscripten", + "defines": ["${defines}"], + "compilerPath": "${env:EMSDK}/upstream/emscripten/emcc", + "intelliSenseMode": "clang-x86", + "cStandard": "c99", + "cppStandard": "c++11", + "includePath": ["${includePath}"] + }, + { + "name": "Emscripten (Win32)", + "defines": ["${defines}"], + "compilerPath": "${env:EMSDK}/upstream/emscripten/emcc.bat", + "intelliSenseMode": "clang-x86", + "cStandard": "c99", + "cppStandard": "c++11", + "includePath": ["${includePath}"] } ], "version": 4 diff --git a/build.js b/build.js index c4b0467..718e5eb 100644 --- a/build.js +++ b/build.js @@ -55,7 +55,9 @@ async function main () { cp(p(wasmoutdir, 'wzasm.js'), asm) fs.writeFileSync(asm, fs.readFileSync(asm, 'utf8').replace('wzasm.js.mem', 'wz.js.mem').replace('wzasm.wasm', 'wz.wasm'), 'utf8') ts.compile(p('tsconfig.esm.json')) - cp(p(wasmoutdir, 'wzasm.js.mem'), p('./lib/esm/util/wz.js.mem')) + if (fs.existsSync(p(wasmoutdir, 'wzasm.js.mem'))) { + cp(p(wasmoutdir, 'wzasm.js.mem'), p('./lib/esm/util/wz.js.mem')) + } await bundler.webpack(tsgoConfig) await bundler.webpack(readConfigNoCache(p('./tsgo.es5.config.js'))) diff --git a/src/properties/WzPngProperty.ts b/src/properties/WzPngProperty.ts index da37a1f..7d6c1c9 100644 --- a/src/properties/WzPngProperty.ts +++ b/src/properties/WzPngProperty.ts @@ -534,8 +534,8 @@ function inflate (data: Uint8Array, len: number): Promise<Uint8Array> { async function inflateWasm (data: Uint8Array, len: number): Promise<Uint8Array> { if (wasmInflate === null) { const wzWasm = await import('../util/wz') - const { Module } = await wzWasm.default() - wasmInflate = Module.inflate + /* const { Module } = */await wzWasm.default() + wasmInflate = wzWasm.inflate } const buf = wasmInflate(data, len) return buf diff --git a/src/util/wz.d.ts b/src/util/wz.d.ts index 85825a5..2f1db9b 100644 --- a/src/util/wz.d.ts +++ b/src/util/wz.d.ts @@ -1,14 +1,17 @@ +/* eslint-disable @typescript-eslint/naming-convention */ // eslint-disable-next-line @typescript-eslint/triple-slash-reference /// <reference types="emscripten" /> declare namespace mod { - export function inflate (data: Uint8Array, len: number): Uint8Array - export function aesEnc (data: Uint8Array, key: Uint8Array): Uint8Array + export function _wz_zlib_inflate (source: number, srclen: number, dest: number, destlen: number): number + export function _wz_aes_ecb_encrypt (data: number, dataLen: number, key: number, out: number, outLen: number): number + export function _malloc (size: number): number + export function _free (ptr: number): void } declare function init (moduleOverrides?: Partial<EmscriptenModule>): Promise<{ Module: typeof mod }> -export declare const inflate: typeof mod.inflate -export declare const aesEnc: typeof mod.aesEnc +export declare function inflate (data: Uint8Array, len: number): Uint8Array +export declare function aesEnc (data: Uint8Array, key: Uint8Array): Uint8Array export default init diff --git a/test/browser.js b/test/browser.js index 52e8529..859cdff 100644 --- a/test/browser.js +++ b/test/browser.js @@ -32,14 +32,27 @@ if (!HTMLCanvasElement.prototype.toBlob) { ]; var input = document.getElementById('file'); + var mapleVersion = wz.WzMapleVersion.GMS; input.addEventListener('change', function (e) { console.log(e.target.files[0]); if (!e.target.files[0]) return; - const f = e.target.files[0]; + var f = e.target.files[0]; - let n = 0; - wz.walkWzFileAsync(f, wz.WzMapleVersion.GMS, function (obj) { + if (f.name.indexOf('.img') !== -1) { + wz.init().then(function () { + var image = wz.WzImage.createFromFile(f, mapleVersion); + image.parseImage().then(function (parsed) { + if (parsed) { + console.log('parsed'); + } + }); + }); + return; + } + + var n = 0; + wz.walkWzFileAsync(f, mapleVersion, function (obj) { return new Promise(function (resolve, reject) { // if (n >= 10) return true // n++ @@ -50,9 +63,9 @@ if (!HTMLCanvasElement.prototype.toBlob) { console.log(n, wz.WzPropertyType[obj.propertyType], obj.fullPath); return resolve(obj.getBytes(false).then(function (buf) { - const blob = new Blob([buf.buffer], { type: 'audio/mp3' }); - const src = URL.createObjectURL(blob); - const audio = new Audio(); + var blob = new Blob([buf.buffer], { type: 'audio/mp3' }); + var src = URL.createObjectURL(blob); + var audio = new Audio(); audio.src = src; audio.play(); diff --git a/wasm/cgen.config.js b/wasm/cgen.config.js index 7603af1..f74c6d2 100644 --- a/wasm/cgen.config.js +++ b/wasm/cgen.config.js @@ -2,20 +2,21 @@ const { defineFunctionConfig } = require('@tybys/cgen') function createTarget (name, asm, isDebug) { const compilerFlags = [ - ...(isDebug ? ['-sDISABLE_EXCEPTION_CATCHING=0'] : []) + // ...(isDebug ? ['-sDISABLE_EXCEPTION_CATCHING=0'] : []) ] const linkerFlags = [ - '--bind', + // '--bind', '-sALLOW_MEMORY_GROWTH=1', + "-sEXPORTED_FUNCTIONS=['_malloc','_free']", ...(asm ? ['-sWASM=0'] : []), - ...(isDebug ? ['-sDISABLE_EXCEPTION_CATCHING=0', '-sSAFE_HEAP=1'] : []) + ...(isDebug ? [/* '-sDISABLE_EXCEPTION_CATCHING=0', */'-sSAFE_HEAP=1'] : []) ] return { name: name, type: 'exe', sources: [ - './src/main.cpp' + './src/main.c' ], defines: [ 'AES256=1', diff --git a/wasm/export.js b/wasm/export.js index 903d248..7e73c5d 100644 --- a/wasm/export.js +++ b/wasm/export.js @@ -1,7 +1,69 @@ exports.inflate = function (data, len) { - return Module.inflate(data, len) + var srclen = data.length >>> 0; + var source = Module._malloc(srclen); + if (!source) throw new Error('malloc failed'); + var memory = new Uint8Array(Module.HEAPU8.buffer, source, srclen); + memory.set(data); + + var destlen = len >>> 0; + var dest = Module._malloc(destlen); + if (!dest) { + Module._free(source); + throw new Error('malloc failed'); + } + var r = Module._wz_zlib_inflate(source, srclen, dest, destlen); + Module._free(source); + if (r === 0) { + var ret = new Uint8Array(Module.HEAPU8.buffer, dest, destlen).slice(); + Module._free(dest); + return ret; + } + Module._free(dest); + throw new Error('Inflate failed'); } exports.aesEnc = function (data, key) { - return Module.aesEnc(data, key) + if (key.length !== 32) { + throw new Error('Invalid key'); + } + + var srclen = data.length >>> 0; + var source = Module._malloc(srclen); + if (!source) throw new Error('malloc failed'); + var srcMemory = new Uint8Array(Module.HEAPU8.buffer, source, srclen); + srcMemory.set(data); + + var outLenPointer = Module._malloc(4); + if (!source) { + Module._free(source); + throw new Error('malloc failed'); + } + Module._wz_aes_ecb_encrypt(source, srclen, 0, 0, outLenPointer); + + var outLen = Module.HEAPU32[outLenPointer >> 2] + var out = Module._malloc(outLen); + if (!out) { + Module._free(source); + Module._free(outLenPointer); + throw new Error('malloc failed'); + } + var keyLen = key.length >>> 0; + var keyPointer = Module._malloc(keyLen); + if (!keyPointer) { + Module._free(source); + Module._free(outLenPointer); + Module._free(out); + throw new Error('malloc failed'); + } + var keyMemory = new Uint8Array(Module.HEAPU8.buffer, keyPointer, keyLen); + keyMemory.set(key); + + Module._wz_aes_ecb_encrypt(source, srclen, keyPointer, out, outLenPointer); + + var ret = new Uint8Array(Module.HEAPU8.buffer, out, outLen).slice(); + Module._free(source); + Module._free(outLenPointer); + Module._free(out); + Module._free(keyPointer); + return ret; } diff --git a/wasm/src/main.c b/wasm/src/main.c new file mode 100644 index 0000000..ab97519 --- /dev/null +++ b/wasm/src/main.c @@ -0,0 +1,72 @@ +#include <stddef.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <emscripten.h> +#include "zlib.h" +#include "openssl/aes.h" + +EMSCRIPTEN_KEEPALIVE +int wz_zlib_inflate(uint8_t *source, + size_t srclen, + uint8_t *dest, + size_t destlen) { + z_stream infstream; + infstream.zalloc = Z_NULL; + infstream.zfree = Z_NULL; + infstream.opaque = Z_NULL; + infstream.avail_in = (uInt) srclen; // size of input + infstream.next_in = (Bytef*) source; // input char array NOLINT + infstream.avail_out = (uInt) destlen; // size of output + infstream.next_out = (Bytef*) dest; // output char array NOLINT + + inflateInit(&infstream); + int r = inflate(&infstream, Z_NO_FLUSH); + inflateEnd(&infstream); + return r; +} + +EMSCRIPTEN_KEEPALIVE +int wz_aes_ecb_encrypt(const uint8_t* data, + size_t data_len, + const uint8_t* key, + uint8_t* out, + size_t* out_len) { + if (data == NULL || out_len == NULL) { + return 1; + } + + uint8_t* data_buf = NULL; + + size_t padding = data_len % 16; + size_t encrypt_len = 0; + if (padding != 0) { + padding = 16 - padding; + encrypt_len = data_len + padding; + if (out == NULL) { + *out_len = encrypt_len; + return 0; + } + if (key == NULL) return 2; + data_buf = (uint8_t*) malloc(encrypt_len); // NOLINT + memcpy(data_buf, data, data_len); + memset(data_buf + data_len, padding, padding); + } else { + encrypt_len = data_len; + if (out == NULL) { + *out_len = encrypt_len; + return 0; + } + if (key == NULL) return 2; + data_buf = (uint8_t*) malloc(encrypt_len); // NOLINT= + memcpy(data_buf, data, data_len); + } + + AES_KEY k; + AES_set_encrypt_key(key, 256, &k); + AES_ecb_encrypt(data_buf, out, &k, AES_ENCRYPT); + + free(data_buf); + + return 0; +} diff --git a/wasm/src/main.cpp b/wasm/src/main.cpp deleted file mode 100644 index 1b0d3ff..0000000 --- a/wasm/src/main.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include <cstring> -#include <cstddef> -#include <cstdint> -#include <stdexcept> -#include <vector> -#include "zlib.h" -#include "openssl/aes.h" - -#include <emscripten/bind.h> -#include <emscripten/val.h> - -int inf(unsigned char *source, size_t srclen, unsigned char *dest, size_t destlen) { - z_stream infstream; - infstream.zalloc = Z_NULL; - infstream.zfree = Z_NULL; - infstream.opaque = Z_NULL; - infstream.avail_in = (uInt)(srclen); // size of input - infstream.next_in = (Bytef *)source; // input char array - infstream.avail_out = (uInt)(destlen); // size of output - infstream.next_out = (Bytef *)dest; // output char array - - inflateInit(&infstream); - int r = inflate(&infstream, Z_NO_FLUSH); - inflateEnd(&infstream); - return r; -} - -emscripten::val js_inflate(emscripten::val u8arr, emscripten::val len) { - int srclen = u8arr["length"].as<int>(); - unsigned char* src = new unsigned char[srclen]; - memset(src, 0, srclen); - for (int i = 0; i < srclen; i++) { - *(src + i) = u8arr[i].as<unsigned char>(); - } - - int dstlen = len.as<int>(); - unsigned char* dst = new unsigned char[dstlen]; - memset(dst, 0, dstlen); - - int r = inf(src, srclen, dst, dstlen); - if (r == 0) { - emscripten::val dstu8arr = emscripten::val::global("Uint8Array").new_(len); - for (int i = 0; i < dstlen; i++) { - dstu8arr.set(emscripten::val(i), emscripten::val(dst[i])); - } - - delete[] src; - delete[] dst; - - return dstu8arr; - } else { - delete[] src; - delete[] dst; - emscripten::val::global("Error").new_(emscripten::val("Inflate failed")).throw_(); - return emscripten::val::undefined(); - } -} - -static size_t pkcs7cut(uint8_t *p, size_t plen) { - uint8_t last = p[plen - 1]; - if (last > 0 && last <= 16) { - for (size_t x = 2; x <= last; x++) { - if (p[plen - x] != last) { - return plen; - } - } - return plen - last; - } - - return plen; -} - -std::vector<uint8_t> enc(const std::vector<uint8_t>& data, - const std::vector<uint8_t>& key) { - if (key.size() != 32) { - throw std::runtime_error("Invalid key"); - } - - size_t dataLength = data.size(); - const uint8_t* strBuf = (const uint8_t*) data.data(); - - uint8_t* dataBuf = nullptr; - - size_t padding = dataLength % 16; - size_t encryptLength = 0; - if (padding != 0) { - padding = 16 - padding; - encryptLength = dataLength + padding; - dataBuf = new uint8_t[encryptLength]; - memcpy(dataBuf, strBuf, dataLength); - memset(dataBuf + dataLength, padding, padding); - } else { - encryptLength = dataLength; - dataBuf = new uint8_t[dataLength]; - memcpy(dataBuf, strBuf, dataLength); - } - - AES_KEY k; - AES_set_encrypt_key(key.data(), 256, &k); - - std::vector<uint8_t> out(encryptLength); - - AES_ecb_encrypt(dataBuf, out.data(), &k, AES_ENCRYPT); - - delete[] dataBuf; - dataBuf = nullptr; - - return out; -} - -emscripten::val js_aes_256_ecb(emscripten::val u8arr, emscripten::val key) { - int srclen = u8arr["length"].as<int>(); - unsigned char* src = new unsigned char[srclen]; - memset(src, 0, srclen); - for (int i = 0; i < srclen; i++) { - *(src + i) = u8arr[i].as<unsigned char>(); - } - - int keylen = key["length"].as<int>(); - unsigned char* k = new unsigned char[keylen]; - memset(k, 0, keylen); - for (int i = 0; i < keylen; i++) { - *(k + i) = key[i].as<unsigned char>(); - } - - std::vector<uint8_t> result = enc(std::vector<uint8_t>(src, src + srclen), std::vector<uint8_t>(k, k + keylen)); - delete[] src; - delete[] k; - - auto l = result.size(); - emscripten::val ret = emscripten::val::global("Uint8Array").new_(l); - for (int i = 0; i < l; i++) { - ret.set(emscripten::val(i), emscripten::val(result[i])); - } - - return ret; -} - -EMSCRIPTEN_BINDINGS(wz) { - emscripten::function("inflate", js_inflate); - emscripten::function("aesEnc", js_aes_256_ecb); -}