forgot that 4 of them were git repos :skull

This commit is contained in:
neon443
2024-09-28 10:26:25 +01:00
parent 689c1e3167
commit 2a8ef5aec9
149 changed files with 45223 additions and 0 deletions

130
games/webEPK/EPKCompiler.js Normal file
View File

@@ -0,0 +1,130 @@
window.compileEPK = function() {
let old = false;
// 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 concatTypedArrays(a, b) {
const c = new (a.constructor)(a.length + b.length);
c.set(a, 0);
c.set(b, a.length);
return c;
}
const textEncoder = new TextEncoder();
function generateLongArray(num) {
return Uint8Array.of((num >>> 56) & 0xFF, (num >>> 48) & 0xFF, (num >>> 40) & 0xFF, (num >>> 32) & 0xFF, (num >>> 24) & 0xFF, (num >>> 16) & 0xFF, (num >>> 8) & 0xFF, num & 0xFF);
}
function generateIntArray(num) {
return Uint8Array.of((num >>> 24) & 0xFF, (num >>> 16) & 0xFF, (num >>> 8) & 0xFF, num & 0xFF);
}
function generateShortArray(num) {
return Uint8Array.of((num >>> 8) & 0xFF, num & 0xFF);
}
function generateUTF(str) {
return concatTypedArrays(generateShortArray(str.length), textEncoder.encode(str));
}
function generateUTFByte(str) {
return concatTypedArrays(Uint8Array.of(str.length), textEncoder.encode(str));
}
let comment = '# eaglercraft package file - generated with ayunWebEPK by ayunami2000';
let commentNew = '\n\n # Eagler EPK v2.0 (c) $$YEAR$$ Calder Young\n # generated with ayunWebEPK by ayunami2000';
let baseEPK = textEncoder.encode('EAGPKG!!');
baseEPK = concatTypedArrays(baseEPK, generateUTF(comment));
let baseEPKNew = textEncoder.encode('EAGPKG$$');
baseEPKNew = concatTypedArrays(baseEPKNew, generateUTFByte('ver2.0'));
baseEPKNew = concatTypedArrays(baseEPKNew, generateUTFByte('my-cool.epk'));
let currentEPK = null;
function processFile(name, type, file) {
if (old) {
currentEPK = currentEPK == null ? generateUTF('<file>') : concatTypedArrays(currentEPK, generateUTF('<file>'));
currentEPK = concatTypedArrays(currentEPK, generateUTF(name));
currentEPK = concatTypedArrays(currentEPK, new Uint8Array(sha1.arrayBuffer(file.buffer)));
currentEPK = concatTypedArrays(currentEPK, generateIntArray(file.byteLength));
currentEPK = concatTypedArrays(currentEPK, file);
currentEPK = concatTypedArrays(currentEPK, generateUTF('</file>'));
} else {
currentEPK = concatTypedArrays(currentEPK, textEncoder.encode(type));
currentEPK = concatTypedArrays(currentEPK, generateUTFByte(name));
currentEPK = concatTypedArrays(currentEPK, generateIntArray(file.byteLength + 5));
currentEPK = concatTypedArrays(currentEPK, generateIntArray(crc32(file)));
currentEPK = concatTypedArrays(currentEPK, file);
currentEPK = concatTypedArrays(currentEPK, textEncoder.encode(':>'));
}
}
function wrapItUp(size) {
if (old) {
currentEPK = concatTypedArrays(currentEPK, generateUTF(' end'));
currentEPK = concatTypedArrays(baseEPK, new Uint8Array(pako.deflate(currentEPK, { level: 9 })));
} else {
let currBaseEPK = baseEPKNew;
currBaseEPK = concatTypedArrays(currBaseEPK, generateUTF(commentNew.replace('$$YEAR$$', new Date().getFullYear())));
currBaseEPK = concatTypedArrays(currBaseEPK, generateLongArray(Date.now()));
currBaseEPK = concatTypedArrays(currBaseEPK, generateIntArray(size + 1));
currBaseEPK = concatTypedArrays(currBaseEPK, Uint8Array.of(90));
currentEPK = concatTypedArrays(currentEPK, textEncoder.encode('END$'));
currentEPK = concatTypedArrays(currBaseEPK, new Uint8Array(pako.deflate(currentEPK, { level: 9 })));
currentEPK = concatTypedArrays(currentEPK, textEncoder.encode(':::YEE:>'));
}
const blob = new Blob([ currentEPK ], { type: 'application/octet-stream' });
currentEPK = null;
old = false;
return blob;
}
return async function(files, oldMode, fileev) {
let onfile = function() {};
if (fileev != null) {
onfile = fileev;
}
currentEPK = null;
old = oldMode;
// files is same format as output from decompiler
if (!old) {
currentEPK = textEncoder.encode('HEAD');
currentEPK = concatTypedArrays(currentEPK, generateUTFByte('file-type'));
const fileType = 'epk/resources';
currentEPK = concatTypedArrays(currentEPK, generateIntArray(fileType.length));
currentEPK = concatTypedArrays(currentEPK, textEncoder.encode(fileType));
currentEPK = concatTypedArrays(currentEPK, Uint8Array.of(62));
}
for (const file of files) {
processFile(file.name, file.type, new Uint8Array(await file.data.arrayBuffer()));
onfile();
}
return wrapItUp(files.length);
};
};

View File

@@ -0,0 +1,256 @@
window.decompileEPK = function() {
let currentOffset = 0;
let numFiles = 0;
let onfile = function() {};
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);
onfile();
}
onfile = function() {};
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);
onfile();
}
onfile = function() {};
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, fileev) {
if (fileev != null) {
onfile = fileev;
}
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);
};
};

29
games/webEPK/LICENSE Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2022, ayunami2000
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

8
games/webEPK/README.md Normal file
View File

@@ -0,0 +1,8 @@
# ayunWebEPK
Compile EPK files in your browser!
Utilizes:
- [JSZip](https://github.com/Stuk/jszip)
- [pako](https://github.com/nodeca/pako)
- [js-sha1](https://github.com/emn178/js-sha1)
- [Nunito Font](https://fonts.google.com/specimen/Nunito)

329
games/webEPK/builder.html Normal file
View File

@@ -0,0 +1,329 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebEPK</title>
<script src="jszip.min.js"></script>
<script src="pako.min.js"></script>
<script src="sha1.min.js"></script>
<script src="EPKDecompiler.js"></script>
<script src="EPKCompiler.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], button[disabled] {
opacity: 0.8;
}
input[type=checkbox] {
-webkit-appearance: initial;
-moz-appearance: initial;
appearance: initial;
width: 1.5em;
height: 1.5em;
margin: 0;
}
input[type=checkbox]:checked {
background-color: #eeeeee;
}
input[type=checkbox]:before {
content: ':>';
text-align: center;
color: #dddddd;
margin-left: 0.3em;
}
input[type=checkbox]:checked:before {
color: #343434;
}
input[type=checkbox]:after {
content: 'No';
color: #dddddd;
margin-left: 0.6em;
}
input[type=checkbox]:checked:after {
content: 'Yes';
}
@supports (-webkit-touch-callout: none) or (background: -webkit-named-image(i)) {
input[type=checkbox]:after {
content: 'no';
}
input[type=checkbox]:checked:after {
content: 'yes';
}
}
@supports (-moz-appearance: none) {
input[type=checkbox]:after {
content: 'No.';
}
input[type=checkbox]:checked:after {
content: 'Yes.';
}
}
::file-selector-button, progress, input[type=checkbox], button, select, input[type=text] {
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;
}
#editor.modei pre, #editor.modei audio, #editor.modea pre, #editor.modea img, #editor.modet img, #editor.modet audio, #editor.model {
display: none;
}
</style>
</head>
<body>
<sub><a href=".">Want to compile one instead?</a></sub>
<br>
<sub><a href="decompile.html">Want to decompile one instead?</a></sub>
<h1>WebEPK</h1>
<p>Customize EPK files in your browser!</p>
Select base .EPK: <input type="file" onchange="selectFile(this);" accept=".epk">
<br>
Compile to legacy format: <input type="checkbox">
<br>
<button onclick="compileIt(document.querySelector('input[type=file]'));" disabled>Compile it!</button>
<br>
<br>
<progress value="0" max="1"></progress>
<a download="my-cool.epk">Download your EPK!</a>
<div id="editor" class="model">
<input type="text" placeholder="Search or enter file name...">
<select></select>
<button onclick="createFile(this.previousElementSibling.previousElementSibling.value);" disabled>Create file...</button>
<button onclick="deleteCurrentFile();">Delete file...</button>
<br>
<h3></h3>
Change this file: <input type="file" onchange="changeFile(this);">
<br>
<img>
<audio controls></audio>
<pre></pre>
</div>
<script>
const downloadLink = document.querySelectorAll('a')[2];
const progressBar = document.querySelector('progress');
const oldBox = document.querySelector('input[type=checkbox]');
const compileBtn = document.querySelector('button');
const editor = document.getElementById('editor');
const dropdown = document.querySelector('select');
const editorName = editor.querySelector('h3');
const editorImage = editor.querySelector('img');
const editorAudio = editor.querySelector('audio');
const editorText = editor.querySelector('pre');
const editorSearch = editor.querySelector('input[type=text]');
editorSearch.value = '';
// https://stackoverflow.com/a/68703218/6917520
function prefix(words) {
// check border cases size 1 array and empty first word)
if (!words[0] || words.length == 1) return words[0] || "";
let i = 0;
// while all words have the same character at position i, increment i
while(words[0][i] && words.every(w => w[i] === words[0][i]))
i++;
// prefix is the substring from the beginning to the last successfully checked i
return words[0].substr(0, i);
}
let epkFiles = [];
let selectedFile = 0;
function selectFile(fileElem) {
epkFiles.length = 0;
downloadLink.removeAttribute('href');
fileElem.disabled = true;
if (fileElem.files.length > 0) {
const epkFile = fileElem.files[0];
const reader = new FileReader();
reader.onload = function(e) {
progressBar.value = 0;
progressBar.max = 1;
window.decompileEPK()(e.target.result, function() {
progressBar.max++;
progressBar.value++;
}).then(function(fileList) {
if (fileList == null) {
alert('Invalid EPK!');
fileElem.value = '';
progressBar.value = 0;
progressBar.max = 1;
fileElem.removeAttribute('disabled');
return;
}
fileElem.value = '';
progressBar.value = 0;
progressBar.max = 1;
fileElem.removeAttribute('disabled');
epkFiles = fileList;
editor.className = '';
refreshDropdown();
compileBtn.removeAttribute('disabled');
});
};
reader.readAsArrayBuffer(epkFile);
} else {
fileElem.removeAttribute('disabled');
}
}
function compileIt(fileElem) {
compileBtn.disabled = true;
fileElem.disabled = true;
oldBox.disabled = true;
progressBar.value = 0;
progressBar.max = epkFiles.length;
window.compileEPK()(epkFiles, oldBox.checked, function() {
progressBar.value++;
}).then(blob => {
if (blob == null) {
alert('Invalid EPK!');
progressBar.value = 0;
progressBar.max = 1;
oldBox.removeAttribute('disabled');
fileElem.removeAttribute('disabled');
return;
}
downloadLink.href = window.URL.createObjectURL(blob);
progressBar.value = 0;
progressBar.max = 1;
oldBox.removeAttribute('disabled');
fileElem.removeAttribute('disabled');
compileBtn.removeAttribute('disabled');
});
}
function filterDropdown(str) {
str = str.trim().toLowerCase();
const queries = str.split(' ');
const opts = [...dropdown.querySelectorAll('option')];
let oldValue = dropdown.value;
let selectedOne = false;
for (const opt of opts) {
opt.selected = false;
opt.hidden = true;
const lowerOptText = opt.text.toLowerCase();
if (lowerOptText == str) {
if (selectedOne) {
selectedOne.selected = false;
}
selectedOne = opt;
opt.selected = true;
opt.hidden = false;
continue;
}
for (const query of queries) {
if (lowerOptText.includes(query)) {
if (!selectedOne) {
selectedOne = opt;
opt.selected = true;
}
opt.hidden = false;
}
}
}
if (dropdown.value != oldValue) {
dropdown.onchange();
}
}
dropdown.onchange = function() {
selectedFile = dropdown.value;
const fileItem = epkFiles[selectedFile];
editorName.innerText = fileItem.name;
const lowerName = fileItem.name.toLowerCase();
if (lowerName.endsWith('.mp3') || lowerName.endsWith('.ogg')) {
editor.className = 'modea';
editorAudio.src = window.URL.createObjectURL(fileItem.data);
} else if (lowerName.endsWith('.png')) {
editor.className = 'modei';
editorImage.src = window.URL.createObjectURL(fileItem.data);
} else {
editor.className = 'modet';
fileItem.data.text().then(t => {
editorText.innerText = t;
});
}
};
function refreshDropdown() {
dropdown.innerHTML = '';
for (const fileInd in epkFiles) {
if (epkFiles[fileInd].type != 'FILE') {
continue;
}
dropdown.add(new Option(epkFiles[fileInd].name, fileInd, false, fileInd == 0));
}
dropdown.onchange();
editorSearch.oninput();
}
function deleteCurrentFile() {
epkFiles.splice(selectedFile, 1);
refreshDropdown();
}
function createFile(name) {
epkFiles.push({
type: 'FILE',
name: name,
data: new Blob([])
});
refreshDropdown();
}
function changeFile(fileElem) {
if (fileElem.files.length > 0) {
epkFiles[selectedFile].data = fileElem.files[0];
fileElem.value = '';
dropdown.onchange();
}
}
editorSearch.oninput = function() {
filterDropdown(editorSearch.value);
if (this.value != '' && epkFiles[this.nextElementSibling.value].name.toLowerCase() != this.value.toLowerCase()) {
editorSearch.nextElementSibling.nextElementSibling.removeAttribute('disabled');
} else {
editorSearch.nextElementSibling.nextElementSibling.disabled = true;
}
};
</script>
</body>
</html>

119
games/webEPK/decompile.html Normal file
View File

@@ -0,0 +1,119 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebEPK</title>
<script src="jszip.min.js"></script>
<script src="pako_inflate.min.js"></script>
<script src="EPKDecompiler.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>
<br>
<sub><a href="builder.html">Want to create one instead?</a></sub>
<h1>WebEPK</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')[2];
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) {
progressBar.value = 0;
progressBar.max = 1;
window.decompileEPK()(e.target.result, function() {
progressBar.max++;
progressBar.value++;
}).then(function(fileList) {
if (fileList == null) {
alert('Invalid EPK!');
fileElem.value = '';
progressBar.value = 0;
progressBar.max = 1;
fileElem.removeAttribute('disabled');
return;
}
const zip = new JSZip();
progressBar.max = fileList.length;
progressBar.value = 0;
for (const file of fileList) {
progressBar.value++;
if (file.type != 'FILE') continue;
zip.file(file.name, file.data);
}
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>

215
games/webEPK/index.html Normal file
View File

@@ -0,0 +1,215 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebEPK</title>
<script src="jszip.min.js"></script>
<script src="pako_deflate.min.js"></script>
<script src="sha1.min.js"></script>
<script src="EPKCompiler.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;
}
input[type=checkbox] {
-webkit-appearance: initial;
-moz-appearance: initial;
appearance: initial;
width: 1.5em;
height: 1.5em;
margin: 0;
}
input[type=checkbox]:checked {
background-color: #eeeeee;
}
input[type=checkbox]:before {
content: ':>';
text-align: center;
color: #dddddd;
margin-left: 0.3em;
}
input[type=checkbox]:checked:before {
color: #343434;
}
input[type=checkbox]:after {
content: 'No';
color: #dddddd;
margin-left: 0.6em;
}
input[type=checkbox]:checked:after {
content: 'Yes';
}
@supports (-webkit-touch-callout: none) or (background: -webkit-named-image(i)) {
input[type=checkbox]:after {
content: 'no';
}
input[type=checkbox]:checked:after {
content: 'yes';
}
}
@supports (-moz-appearance: none) {
input[type=checkbox]:after {
content: 'No.';
}
input[type=checkbox]:checked:after {
content: 'Yes.';
}
}
::file-selector-button, progress, input[type=checkbox] {
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="decompile.html">Want to decompile one instead?</a></sub>
<br>
<sub><a href="builder.html">Want to create one instead?</a></sub>
<h1>WebEPK</h1>
<p>Compile EPK files in your browser!</p>
Use legacy format: <input type="checkbox">
<br>
Select resources folder: <input type="file" onchange="selectFile(this);" directory webkitdirectory>
<br>
<i>~or~</i>
<br>
Select .ZIP archive of resources: <input type="file" onchange="selectFile(this);" accept=".zip">
<br>
<progress value="0" max="1"></progress>
<a download="my-cool.epk">Download your EPK!</a>
<script>
const downloadLink = document.querySelectorAll('a')[2];
const progressBar = document.querySelector('progress');
const fileElems = document.querySelectorAll('input[type=file]');
const oldBox = document.querySelector('input[type=checkbox]');
// https://stackoverflow.com/a/68703218/6917520
function prefix(words) {
// check border cases size 1 array and empty first word)
if (!words[0] || words.length == 1) return words[0] || "";
let i = 0;
// while all words have the same character at position i, increment i
while(words[0][i] && words.every(w => w[i] === words[0][i]))
i++;
// prefix is the substring from the beginning to the last successfully checked i
return words[0].substr(0, i);
}
function selectFile(fileElem) {
downloadLink.removeAttribute('href');
fileElems.forEach(elem => elem.disabled = true);
oldBox.disabled = true;
const fileArr = [];
if (fileElem.files.length == 1) {
const zipFile = fileElem.files[0];
const reader = new FileReader();
reader.onload = function(e) {
const zip = new JSZip();
zip.loadAsync(e.target.result).then(function(zip) {
progressBar.max = Object.keys(zip.files).length;
const stripFolder = prefix(Object.keys(zip.files));
let i = 0;
for (let fileName in zip.files) {
if (fileName.endsWith('/')) {
progressBar.value = i++;
if (i == progressBar.max) {
finishIt(fileElem, fileArr, oldBox.checked);
}
continue;
}
zip.files[fileName].async('blob').then(function(data) {
fileArr.push({
type: 'FILE',
name: stripFolder.length == 0 ? fileName : fileName.slice(fileName.indexOf(stripFolder) + stripFolder.length),
data: data
});
progressBar.value = i++;
if (i == progressBar.max) {
finishIt(fileElem, fileArr, oldBox.checked);
}
});
}
});
};
reader.readAsArrayBuffer(zipFile);
} else if (fileElem.files.length > 1) {
progressBar.max = fileElem.files.length;
const stripFolder = prefix(Object.values(fileElem.files).map(file => file.webkitRelativePath));
let i = 0;
for (let file of fileElem.files) {
const fileName = file.webkitRelativePath;
fileArr.push({
type: 'FILE',
name: stripFolder.length == 0 ? fileName : fileName.slice(fileName.indexOf(stripFolder) + stripFolder.length),
data: file
});
progressBar.value = i++;
if (i == progressBar.max) {
finishIt(fileElem, fileArr, oldBox.checked);
}
}
} else {
fileElems.forEach(elem => elem.removeAttribute('disabled'));
oldBox.removeAttribute('disabled');
}
}
function finishIt(fileElem, fileArr, old) {
progressBar.value = 0;
window.compileEPK()(fileArr, old, function() {
progressBar.value++;
}).then(blob => {
downloadLink.href = window.URL.createObjectURL(blob);
currentEPK = null;
fileElem.value = '';
progressBar.value = 0;
progressBar.max = 1;
fileElems.forEach(elem => elem.removeAttribute('disabled'));
oldBox.removeAttribute('disabled');
});
}
</script>
</body>
</html>

13
games/webEPK/jszip.min.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

2
games/webEPK/pako.min.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

2
games/webEPK/pako_deflate.min.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

2
games/webEPK/pako_inflate.min.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

9
games/webEPK/sha1.min.js vendored Normal file
View File

File diff suppressed because one or more lines are too long