mirror of
https://github.com/ayunami2000/ayunWebEPK.git
synced 2024-11-23 11:06:06 -08:00
Add EPK decompiler
todo: SHA1 checks for decompiling old format EPK
This commit is contained in:
parent
84d25525b1
commit
5d2f7ba203
244
ayunEPKDecompiler.js
Normal file
244
ayunEPKDecompiler.js
Normal file
|
@ -0,0 +1,244 @@
|
|||
window.decompileEPK = function () {
|
||||
let currentOffset = 0;
|
||||
|
||||
let numFiles = 0;
|
||||
|
||||
function detectOldHeader(epkData) {
|
||||
const oldHeader = "EAGPKG!!";
|
||||
for (let i = 0; i < oldHeader.length; i++) {
|
||||
if (epkData[i] != oldHeader.charCodeAt(i)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function readASCII(epkData) {
|
||||
const len = read(epkData);
|
||||
let str = "";
|
||||
for (let i = 0; i < len; i++) {
|
||||
str += String.fromCharCode(read(epkData));
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function read(epkData) {
|
||||
return epkData[currentOffset++];
|
||||
}
|
||||
|
||||
function skip(num) {
|
||||
currentOffset += num;
|
||||
}
|
||||
|
||||
function loadShort(epkData) {
|
||||
return (read(epkData) << 8) | read(epkData);
|
||||
}
|
||||
|
||||
function loadInt(epkData) {
|
||||
return (read(epkData) << 24) | (read(epkData) << 16) | (read(epkData) << 8) | read(epkData);
|
||||
}
|
||||
|
||||
function readUTF(epkData) {
|
||||
const len = loadShort(epkData);
|
||||
let str = "";
|
||||
for (let i = 0; i < len; i++) {
|
||||
str += String.fromCharCode(read(epkData));
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/18639903/6917520
|
||||
|
||||
const crc32 = (function() {
|
||||
let table = new Uint32Array(256);
|
||||
|
||||
for (let i = 256; i--;) {
|
||||
let tmp = i;
|
||||
|
||||
for (let k = 8; k--;) {
|
||||
tmp = tmp & 1 ? 3988292384 ^ tmp >>> 1 : tmp >>> 1;
|
||||
}
|
||||
|
||||
table[i] = tmp;
|
||||
}
|
||||
|
||||
return function(data) {
|
||||
let crc = -1;
|
||||
|
||||
for (let i = 0, l = data.length; i < l; i++) {
|
||||
crc = crc >>> 8 ^ table[crc & 255 ^ data[i]];
|
||||
}
|
||||
|
||||
return (crc ^ -1) >>> 0;
|
||||
};
|
||||
})();
|
||||
|
||||
function decompileOld(epkData) {
|
||||
readUTF(epkData);
|
||||
|
||||
try {
|
||||
let zData = pako.inflate(epkData.slice(currentOffset));
|
||||
currentOffset = 0;
|
||||
return readFilesOld(zData);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function decompileNew(epkData) {
|
||||
const vers = readASCII(epkData);
|
||||
|
||||
if (!vers.startsWith("ver2.")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
skip(read(epkData));
|
||||
skip(loadShort(epkData));
|
||||
skip(8);
|
||||
|
||||
numFiles = loadInt(epkData);
|
||||
|
||||
const compressionType = String.fromCharCode(read(epkData));
|
||||
|
||||
let zData = epkData.slice(currentOffset);
|
||||
|
||||
if (compressionType == "Z" || compressionType == "G") {
|
||||
try {
|
||||
zData = pako.inflate(zData);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
} else if (compressionType == "0") {
|
||||
// do nothing
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
currentOffset = 0;
|
||||
|
||||
return readFilesNew(zData);
|
||||
}
|
||||
|
||||
function readFilesOld(data) {
|
||||
let files = [];
|
||||
|
||||
let file;
|
||||
while ((file = readFileOld(data)) != null) {
|
||||
if (file == -1) return null;
|
||||
files.push(file);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function readFileOld(data) {
|
||||
const s = readUTF(data);
|
||||
if (s == " end") {
|
||||
return null;
|
||||
} else if (s != "<file>") {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const path = readUTF(data);
|
||||
|
||||
skip(20);
|
||||
|
||||
const len = loadInt(data);
|
||||
const blob = new Blob([data.slice(currentOffset, currentOffset + len)]);
|
||||
skip(len);
|
||||
if (readUTF(data) != "</file>") {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return {
|
||||
type: "FILE",
|
||||
name: path,
|
||||
data: blob
|
||||
};
|
||||
}
|
||||
|
||||
function readFilesNew(data) {
|
||||
let files = [];
|
||||
|
||||
let file;
|
||||
while ((file = readFileNew(data)) != null) {
|
||||
if (file == -1) return null;
|
||||
files.push(file);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function readFileNew(data) {
|
||||
const type = String.fromCharCode(read(data), read(data), read(data), read(data));
|
||||
|
||||
if (numFiles == 0) {
|
||||
if (type != "END$") {
|
||||
return -1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type == "END$") {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const name = readASCII(data);
|
||||
const len = loadInt(data);
|
||||
let blob = null;
|
||||
|
||||
if (type == "FILE") {
|
||||
if (len < 5) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const crc = loadInt(data);
|
||||
const blobBuffer = data.slice(currentOffset, currentOffset + len - 5);
|
||||
skip(len - 5);
|
||||
blob = new Blob([blobBuffer]);
|
||||
|
||||
if (crc != (crc32(blobBuffer) | 0)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (read(data) != 58) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
blob = new Blob([data.slice(currentOffset, currentOffset + len)]);
|
||||
skip(len);
|
||||
}
|
||||
|
||||
if (read(data) != 62) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
numFiles--;
|
||||
|
||||
return {
|
||||
type: type,
|
||||
name: name,
|
||||
data: blob
|
||||
};
|
||||
}
|
||||
|
||||
return async function(rawBuffer) {
|
||||
let epkData = new Uint8Array(rawBuffer);
|
||||
|
||||
if (detectOldHeader(epkData)) {
|
||||
epkData = epkData.slice(8);
|
||||
return decompileOld(epkData);
|
||||
}
|
||||
|
||||
const header = "EAGPKG$$";
|
||||
for (let i = 0; i < header.length; i++) {
|
||||
if (epkData[i] != header.charCodeAt(i)) return null;
|
||||
}
|
||||
|
||||
const endCode = ":::YEE:>";
|
||||
for (let i = 0; i < endCode.length; i++) {
|
||||
if (epkData[epkData.length - 8 + i] != endCode.charCodeAt(i)) return null;
|
||||
}
|
||||
|
||||
epkData = epkData.slice(8, -8);
|
||||
return decompileNew(epkData);
|
||||
};
|
||||
};
|
109
decompile.html
Normal file
109
decompile.html
Normal file
|
@ -0,0 +1,109 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script async src="https://arc.io/widget.min.js#HEPCFa3K"></script>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>ayunWebEPK</title>
|
||||
<script type="text/javascript" src="jszip.min.js"></script>
|
||||
<script type="text/javascript" src="pako_inflate.min.js"></script>
|
||||
<script type="text/javascript" src="ayunEPKDecompiler.js"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
a:not([href]) {
|
||||
display: none;
|
||||
}
|
||||
*, ::file-selector-button {
|
||||
font-family: 'Nunito', sans-serif;
|
||||
}
|
||||
body {
|
||||
background-color: #111111;
|
||||
color: #dddddd;
|
||||
}
|
||||
input[disabled] {
|
||||
opacity: 0.8;
|
||||
}
|
||||
::file-selector-button, progress {
|
||||
background-color: #343434;
|
||||
color: #eeeeee;
|
||||
border: 1px solid #eeeeee;
|
||||
border-radius: 4px;
|
||||
}
|
||||
a, input[type=file] {
|
||||
color: #dddddd;
|
||||
}
|
||||
progress {
|
||||
height: 1em;
|
||||
}
|
||||
progress, ::-webkit-file-upload-button {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
progress[value="0"][max="1"] {
|
||||
display: none;
|
||||
}
|
||||
::-moz-progress-bar {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
::-webkit-progress-value {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
::-webkit-progress-bar {
|
||||
background-color: #343434;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<sub><a href="..">Want to compile one instead?</a></sub>
|
||||
<h1>ayunWebEPK</h1>
|
||||
<p>Decompile EPK files in your browser!</p>
|
||||
Select .EPK file: <input type="file" onchange="selectFile(this);" accept=".epk"/>
|
||||
<br/>
|
||||
<progress value="0" max="1"></progress>
|
||||
<a download="my-cool.zip">Download as a ZIP!</a>
|
||||
<script>
|
||||
const downloadLink = document.querySelectorAll('a')[1];
|
||||
const progressBar = document.querySelector('progress');
|
||||
|
||||
function selectFile(fileElem) {
|
||||
downloadLink.removeAttribute('href');
|
||||
fileElem.disabled = true;
|
||||
if (fileElem.files.length > 0) {
|
||||
const epkFile = fileElem.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
window.decompileEPK()(e.target.result).then(function(fileList) {
|
||||
if (fileList == null) {
|
||||
alert("Invalid EPK!");
|
||||
fileElem.removeAttribute('disabled');
|
||||
return;
|
||||
}
|
||||
const zip = new JSZip();
|
||||
progressBar.max = fileList.length;
|
||||
for (const file of fileList) {
|
||||
progressBar.value++;
|
||||
if (file.type != 'FILE') continue;
|
||||
zip.file(file.name, file.data); // todo: check if it'll accept a blob as input
|
||||
}
|
||||
|
||||
zip.generateAsync({type: 'blob'}).then(function(content) {
|
||||
downloadLink.href = window.URL.createObjectURL(content);
|
||||
fileElem.value = '';
|
||||
progressBar.value = 0;
|
||||
progressBar.max = 1;
|
||||
fileElem.removeAttribute('disabled')
|
||||
});
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(epkFile);
|
||||
} else {
|
||||
fileElem.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -102,6 +102,7 @@
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<sub><a href="decompile.html">Want to decompile one instead?</a></sub>
|
||||
<h1>ayunWebEPK</h1>
|
||||
<p>Compile EPK files in your browser!</p>
|
||||
Use legacy format: <input type="checkbox"/>
|
||||
|
@ -142,7 +143,7 @@
|
|||
};
|
||||
})();
|
||||
|
||||
const downloadLink = document.querySelector('a');
|
||||
const downloadLink = document.querySelectorAll('a')[1];
|
||||
const progressBar = document.querySelector('progress');
|
||||
const fileElems = document.querySelectorAll('input[type=file]');
|
||||
const oldBox = document.querySelector('input[type=checkbox]');
|
||||
|
|
2
pako_inflate.min.js
vendored
Normal file
2
pako_inflate.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user