Merge pull request #12 from eaglerforge/main

Update stable to latest build
This commit is contained in:
ZXMushroom63 2024-09-22 19:41:10 +08:00 committed by GitHub
commit d0853bcaa1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 210 additions and 816 deletions

View File

@ -1,4 +1,4 @@
## Quirks in TeaVM ## TeaVM Quirks and Caveats
When TeaVM compiles code, it sometimes does strange things. When TeaVM compiles code, it sometimes does strange things.
#### Property Suffixes #### Property Suffixes
@ -14,4 +14,15 @@ Update 13/09/2024:
Any form of incorrect data type, even passing the wrong values, can cause this sort of hang. I encountered this when trying to set a block in the world to any form of wood or leaf block, without adding iproperties to the tree type. Any form of incorrect data type, even passing the wrong values, can cause this sort of hang. I encountered this when trying to set a block in the world to any form of wood or leaf block, without adding iproperties to the tree type.
Update 13/09/2024: Update 13/09/2024:
Calling methods while the TeaVM thread is in a critical transition state (see `ModAPI.util.isCritical()`) will shift the call stack, cause methods to access the incorrect values at runtime, and also cause the stack to implode. Gotta love TeaVM. Calling methods while the TeaVM thread is in a critical transition state (see `ModAPI.util.isCritical()`) will shift the call stack, cause methods to access the incorrect values at runtime, and also cause the stack to implode. Gotta love TeaVM.
Update 22/09/2024:
See Asynchronous Code
#### TeaVM thread suspension/resumption
TeaVM allows for writing asynchronous callbacks, which eaglercraft uses for file operations and downloading from URIs. However, when a method that makes use of an async callback gets run from ModAPI, it triggers a stack implosion due to mismatches in value types upon return (as well as a whole other myriad of symptoms). Currently this is not supported by ModAPI, and it will take some time until it will be. In the meanwhile, avoid using constructors or methods that access a file or use other asynchronous apis. Examples:
- Constructing an EntityPlayerMP
- Setting blocks in the world in some occasions
Potential workarounds: This isn't confirmed yet, but there is a probable chance that overriding or patching methods in classes like VFile2 or PlatformFilesystem is a viable workaround. (22/09/2024).
I'll be verifying this is the future and if it is possible, releasing a library for it. (the maybe-library is going to be called AsyncSink if it will exist)

View File

@ -17,8 +17,9 @@
// Get the EntityPlayerMP class to spawn the fake player // Get the EntityPlayerMP class to spawn the fake player
const EntityPlayerMPClass = ModAPI.reflect.getClassById("net.minecraft.entity.player.EntityPlayerMP"); const EntityPlayerMPClass = ModAPI.reflect.getClassById("net.minecraft.entity.player.EntityPlayerMP");
console.log(ModAPI.server.getConfigurationManager());
const fakePlayer = EntityPlayerMPClass.constructors[0]( const fakePlayer = EntityPlayerMPClass.constructors[0](
world.getMinecraftServer(), world.getRef(), fakeProfile, playerInteractionManager ModAPI.server.getRef(), world.getRef(), fakeProfile, playerInteractionManager
); );
// Set the fake player position to be near the command sender // Set the fake player position to be near the command sender

View File

@ -0,0 +1,63 @@
//SUCCESS - While there is no TeaVM thread actively running, I am able to run an asyncronous function, and get a result.
ModAPI.hooks._teavm.$rt_startThread(() => {
return ModAPI.hooks.methods.nlevi_PlatformRuntime_downloadRemoteURI(ModAPI.util.str("data:text/plain,hi"))
}, function (...args) { console.log(this, args) })
//WIP - Pausing and resuming client thread
globalThis.suspendTest = function (...args) {
if (!ModAPI.util.isCritical()) {
var thread = ModAPI.hooks._teavm.$rt_nativeThread();
var javaThread = ModAPI.hooks._teavm.$rt_getThread();
globalThis.testThread = thread;
console.log("BeforeAnything: ", thread.stack);
thread.suspend(function () {
console.log("Pausing for 10 seconds.", thread.stack);
setTimeout(function () {
console.log("Resuming...", thread.stack);
ModAPI.hooks._teavm.$rt_setThread(javaThread);
thread.resume();
console.log("After resume: ", thread.stack);
}, 10000);
});
}
return suspendTest.apply(this, args);
}
function jl_Thread_sleep$_asyncCall_$(millis) {
var thread = $rt_nativeThread();
var javaThread = $rt_getThread();
var callback = function () { };
callback.$complete = function (val) {
thread.attribute = val;
$rt_setThread(javaThread);
thread.resume();
};
callback.$error = function (e) {
thread.attribute = $rt_exception(e);
$rt_setThread(javaThread);
thread.resume();
};
callback = otpp_AsyncCallbackWrapper_create(callback);
thread.suspend(function () {
try {
jl_Thread_sleep0(millis, callback);
} catch ($e) {
callback.$error($rt_exception($e));
}
});
return null;
}
function jl_Thread_sleep0($millis, $callback) {
var $current, $handler;
$current = jl_Thread_currentThread();
$handler = new jl_Thread$SleepHandler;
$handler.$thread = $current;
$handler.$callback = $callback;
$handler.$scheduleId = otp_Platform_schedule($handler, Long_ge($millis, Long_fromInt(2147483647)) ? 2147483647 : Long_lo($millis));
$current.$interruptHandler = $handler;
}

View File

@ -89,6 +89,8 @@
<label class="custom-file-label" for="htmlFile" <label class="custom-file-label" for="htmlFile"
>Choose .html file...</label >Choose .html file...</label
> >
<br />
<span><code id="status">Awaiting input...</code></span>
<br /><br /> <br /><br />
<button class="btn btn-primary" id="giveme">Make modded client</button> <button class="btn btn-primary" id="giveme">Make modded client</button>
<button class="btn btn-primary" id="givemeserver" disabled> <button class="btn btn-primary" id="givemeserver" disabled>
@ -123,7 +125,9 @@
</details> </details>
<details> <details>
<summary>Roadmap?</summary> <summary>Roadmap?</summary>
<a href="https://eaglerforge.github.io/EaglerForgeInjector/roadmap/">roadmap.</a> <a href="https://eaglerforge.github.io/EaglerForgeInjector/roadmap/"
>roadmap.</a
>
</details> </details>
<details> <details>
<summary>How does this tool work?</summary> <summary>How does this tool work?</summary>
@ -143,7 +147,9 @@
</div> </div>
</div> </div>
<script src="filesaver.min.js"></script> <!-- Libraries -->
<script src="libs/babel.min.js"></script>
<script src="libs/filesaver.min.js"></script>
<script> <script>
document.querySelector("#htmlFile").addEventListener("input", (e) => { document.querySelector("#htmlFile").addEventListener("input", (e) => {
@ -166,10 +172,12 @@
`; `;
var freezeCallstack = `if(ModAPI.hooks.freezeCallstack){return false};`; var freezeCallstack = `if(ModAPI.hooks.freezeCallstack){return false};`;
</script> </script>
<script src="injector.stepAsync.js"></script>
<script src="injector.js"></script> <script src="injector.js"></script>
<!-- Code assets --> <!-- Code assets -->
<script src="postinit.injector.js"></script> <script src="postinit.js"></script>
<script src="postinit.async.js"></script>
<script src="modloader.injector.js"></script> <script src="modloader.injector.js"></script>
<script src="modgui.injector.js"></script> <script src="modgui.injector.js"></script>
<script src="efserver.js"></script> <script src="efserver.js"></script>

View File

@ -1,3 +1,11 @@
function wait(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve(); }, ms);
});
}
function _status(x) {
document.querySelector("#status").innerText = x;
}
function entriesToStaticVariableProxy(entries, prefix) { function entriesToStaticVariableProxy(entries, prefix) {
var getComponents = ""; var getComponents = "";
entries.forEach((entry) => { entries.forEach((entry) => {
@ -45,7 +53,9 @@ function entriesToStaticVariableProxy(entries, prefix) {
});`; });`;
return proxy; return proxy;
} }
function processClasses(string) { async function processClasses(string) {
_status("Beginning patch process...");
await wait(50);
var patchedFile = string; var patchedFile = string;
patchedFile = patchedFile.replaceAll( patchedFile = patchedFile.replaceAll(
`(function(root, module) {`, `(function(root, module) {`,
@ -57,6 +67,10 @@ function processClasses(string) {
`${modapi_preinit} `${modapi_preinit}
var main;(function(){` var main;(function(){`
); );
_status("Patching threads and reflect metadata...");
await wait(50);
patchedFile = patchedFile patchedFile = patchedFile
.replace("\r", "") .replace("\r", "")
.replace( .replace(
@ -95,15 +109,10 @@ var main;(function(){`
} }
); );
patchedFile = patchedFile.replace( patchedFile = patchedFile.replaceAll("function TeaVMThread(", "globalThis.ModAPI.hooks.TeaVMThread = TeaVMThread;\nfunction TeaVMThread(");
` id="game_frame">`,
` id="game_frame"> _status("Extracting constructors and methods...");
\<script id="modapi_postinit"\>${globalThis.modapi_postinit}\<\/script\> await wait(50);
\<script id="modapi_modloader"\>${globalThis.modapi_modloader}\<\/script\>
\<script id="modapi_guikit"\>${globalThis.modapi_guikit}\<\/script\>
\<script id="modapi_postinit_data"\>globalThis.modapi_postinit = \`${globalThis.modapi_postinit}\`;\<\/script\>
\<script id="libserverside"\>{"._|_libserverside_|_."}\<\/script\>`
);
const extractConstructorRegex = const extractConstructorRegex =
/^\s*function (\S*?)__init_\d*?\((?!\$)/gm; /^\s*function (\S*?)__init_\d*?\((?!\$)/gm;
@ -208,7 +217,8 @@ var main;(function(){`
return proxy + "\n" + match; return proxy + "\n" + match;
} }
); );
_status("Extracting teavm internals...");
await wait(50);
patchedFile = patchedFile.replaceAll( patchedFile = patchedFile.replaceAll(
/function \$rt_\S+?\(/gm, /function \$rt_\S+?\(/gm,
(match) => { (match) => {
@ -220,9 +230,28 @@ var main;(function(){`
); );
} }
); );
_status("Applying async override...");
await wait(50);
//patchedFile = await asyncify(patchedFile);
_status("Injecting scripts...");
await wait(50);
patchedFile = patchedFile.replace(
` id="game_frame">`,
` id="game_frame">
\<script id="modapi_postinit"\>${globalThis.modapi_postinit}\<\/script\>
\<script id="modapi_postinitasync"\>${globalThis.modapi_postinitasync}\<\/script\>
\<script id="modapi_modloader"\>${globalThis.modapi_modloader}\<\/script\>
\<script id="modapi_guikit"\>${globalThis.modapi_guikit}\<\/script\>
\<script id="modapi_postinit_data"\>globalThis.modapi_postinit = \`${globalThis.modapi_postinit}\`;\<\/script\>
\<script id="libserverside"\>{"._|_libserverside_|_."}\<\/script\>`
);
patchedFile = patchedFile.replaceAll(/main\(\);\s*?}/gm, (match) => { patchedFile = patchedFile.replaceAll(/main\(\);\s*?}/gm, (match) => {
return match.replace("main();", "main();ModAPI.hooks._postInit();"); return match.replace("main();", "main();ModAPI.hooks._postInit();");
}); });
_status("Done, awaiting input...");
await wait(50);
return patchedFile; return patchedFile;
} }
@ -238,8 +267,8 @@ document.querySelector("#giveme").addEventListener("click", () => {
var fileType = file.name.split("."); var fileType = file.name.split(".");
fileType = fileType[fileType.length - 1]; fileType = fileType[fileType.length - 1];
file.text().then((string) => { file.text().then(async (string) => {
var patchedFile = processClasses(string); var patchedFile = await processClasses(string);
patchedFile.replace(`{"._|_libserverside_|_."}`) patchedFile.replace(`{"._|_libserverside_|_."}`)
var blob = new Blob([patchedFile], { type: file.type }); var blob = new Blob([patchedFile], { type: file.type });
saveAs(blob, "processed." + fileType); saveAs(blob, "processed." + fileType);
@ -258,8 +287,8 @@ document.querySelector("#givemeserver").addEventListener("click", () => {
var fileType = file.name.split("."); var fileType = file.name.split(".");
fileType = fileType[fileType.length - 1]; fileType = fileType[fileType.length - 1];
file.text().then((string) => { file.text().then(async (string) => {
var patchedFile = processClasses(string); var patchedFile = await processClasses(string);
patchedFile.replace(`{"._|_libserverside_|_."}`, `(${EFServer.toString()})()`); patchedFile.replace(`{"._|_libserverside_|_."}`, `(${EFServer.toString()})()`);
var blob = new Blob([patchedFile], { type: file.type }); var blob = new Blob([patchedFile], { type: file.type });
saveAs(blob, "efserver." + fileType); saveAs(blob, "efserver." + fileType);

61
injector.stepAsync.js Normal file
View File

@ -0,0 +1,61 @@
// Babel plugin to transform functions and calls
const ASYNC_PLUGIN_1 = function ({ types: t }) {
return {
visitor: {
FunctionDeclaration(path) {
console.log(path);
path.node.async = true;
},
ArrowFunctionExpression(path) {
console.log(path);
path.node.async = true;
},
CallExpression(path) {
console.log(path);
if (path.parent.type !== 'AwaitExpression') {
path.replaceWith(
t.awaitExpression(path.node)
);
}
}
}
};
};
async function asyncify(input) {
let isHtml = true;
let inputHtml = input;
// Check if the input is raw JavaScript
if (!input.trim().startsWith('<')) {
isHtml = false;
inputHtml = `<script>${input}</script>`;
}
_status("[ASYNC_PLUGIN_1] Parsing html...");
await wait(50);
const parser = new DOMParser();
const doc = parser.parseFromString(inputHtml, 'text/html');
const scriptTags = doc.querySelectorAll('script');
for (let i = 0; i < scriptTags.length; i++) {
const scriptTag = scriptTags[i];
const code = scriptTag.textContent;
_status("[ASYNC_PLUGIN_1] Transpiling script #" + (i + 1) + " of length " + Math.round(code.length / 1000) + "k...");
await wait(50);
const output = Babel.transform(code, {
plugins: [ASYNC_PLUGIN_1]
});
scriptTag.textContent = output.code;
}
_status("[ASYNC_PLUGIN_1] Job complete!");
await wait(50);
if (isHtml) {
return doc.documentElement.outerHTML;
} else {
return doc.querySelector('script').textContent;
}
}

2
libs/babel.min.js vendored Normal file

File diff suppressed because one or more lines are too long

4
postinit.async.js Normal file
View File

@ -0,0 +1,4 @@
globalThis.modapi_postinitasync = "(" + (() => {
//EaglerForge post initialization code for async.
}).toString() + ")();";

View File

@ -1,790 +0,0 @@
globalThis.modapi_postinit = `(() => {
//EaglerForge post initialization code.
//This script cannot contain backticks, escape characters, or backslashes in order to inject into the dedicated server code.
var startedModLoader = false;
function startModLoader() {
if (!startedModLoader) {
startedModLoader = true;
modLoader([]);
}
}
ModAPI.hooks._classMap = {};
globalThis.PluginAPI ||= ModAPI;
ModAPI.mcinstance ||= {};
ModAPI.javaClient ||= {};
ModAPI.meta = {};
ModAPI.meta._titleMap = {};
ModAPI.meta._descriptionMap = {};
ModAPI.meta._developerMap = {};
ModAPI.meta._iconMap = {};
ModAPI.meta._versionMap = {};
ModAPI.array = {};
function limitSize(x, n) {
if (x.length > n) {
return x.substring(0, n) + "…";
} else {
return x;
}
}
ModAPI.meta.title = function (title) {
if (!document.currentScript || document.currentScript.getAttribute("data-isMod") !== "true") {
return console.log("[ModAPIMeta] Cannot set meta for non-mod script.");
}
if (!document.currentScript.hasAttribute("data-hash")) {
return console.log("[ModAPIMeta] Script does not have a hashcode.");
}
ModAPI.meta._titleMap[document.currentScript.getAttribute("data-hash")] = limitSize(title, 16);
}
ModAPI.meta.icon = function (iconSrc) {
if (!document.currentScript || document.currentScript.getAttribute("data-isMod") !== "true") {
return console.log("[ModAPIMeta] Cannot set meta for non-mod script.");
}
if (!document.currentScript.hasAttribute("data-hash")) {
return console.log("[ModAPIMeta] Script does not have a hashcode.");
}
ModAPI.meta._iconMap[document.currentScript.getAttribute("data-hash")] = iconSrc;
}
ModAPI.meta.credits = function (cd) {
if (!document.currentScript || document.currentScript.getAttribute("data-isMod") !== "true") {
return console.log("[ModAPIMeta] Cannot set meta for non-mod script.");
}
if (!document.currentScript.hasAttribute("data-hash")) {
return console.log("[ModAPIMeta] Script does not have a hashcode.");
}
ModAPI.meta._developerMap[document.currentScript.getAttribute("data-hash")] = limitSize(cd, 36);
}
ModAPI.meta.description = function (desc) {
if (!document.currentScript || document.currentScript.getAttribute("data-isMod") !== "true") {
return console.log("[ModAPIMeta] Cannot set meta for non-mod script.");
}
if (!document.currentScript.hasAttribute("data-hash")) {
return console.log("[ModAPIMeta] Script does not have a hashcode.");
}
ModAPI.meta._descriptionMap[document.currentScript.getAttribute("data-hash")] = limitSize(desc, 160);
}
ModAPI.meta.version = function (ver) {
if (!document.currentScript || document.currentScript.getAttribute("data-isMod") !== "true") {
return console.log("[ModAPIMeta] Cannot set meta for non-mod script.");
}
if (!document.currentScript.hasAttribute("data-hash")) {
return console.log("[ModAPIMeta] Script does not have a hashcode.");
}
ModAPI.meta._versionMap[document.currentScript.getAttribute("data-hash")] = limitSize(ver, 6);
}
ModAPI.reflect ||= {};
ModAPI.server = ModAPI.serverInstance = null;
ModAPI.dedicatedServer ||= {};
ModAPI.dedicatedServer._data ||= [];
ModAPI.dedicatedServer._wasUsed = false;
ModAPI.dedicatedServer.appendCode = function (code) {
if (ModAPI.dedicatedServer._wasUsed) {
return console.warn("The dedicated server has already launched, ModAPI.dedicatedServer.appendCode() is useless.");
}
if (typeof code === "function") {
ModAPI.dedicatedServer._data.push("(" + code.toString() + ")()");
} else if (typeof code === "string") {
ModAPI.dedicatedServer._data.push(code);
}
}
ModAPI.util ||= {};
ModAPI.util.getMethodFromPackage = function (classId, methodName) {
var name = "";
var classStuff = classId.split(".");
classStuff.forEach((component, i) => {
if (i === classStuff.length - 1) {
name += "_" + component;
} else {
name += component[0].toLowerCase();
}
});
name += "_" + methodName;
return name;
}
ModAPI.util.getCompiledNameFromPackage = ModAPI.util.getCompiledName = function (classId) {
var name = "";
var classStuff = classId.split(".");
classStuff.forEach((component, i) => {
if (i === classStuff.length - 1) {
name += "_" + component;
} else {
name += component[0].toLowerCase();
}
});
return name;
}
ModAPI.util.wrap = function (outputValue, target) {
if (outputValue && typeof outputValue === "object" && Array.isArray(outputValue.data) && typeof outputValue.type === "function") {
return new Proxy(outputValue.data, ModAPI.util.TeaVMArray_To_Recursive_BaseData_ProxyConf);
}
if (outputValue && typeof outputValue === "object" && !Array.isArray(outputValue)) {
return new Proxy(outputValue, ModAPI.util.TeaVM_to_Recursive_BaseData_ProxyConf);
}
if (outputValue && typeof outputValue === "function" && target) {
return function (...args) {
var xOut = outputValue.apply(target, args);
if (xOut && typeof xOut === "object" && Array.isArray(xOut.data) && typeof outputValue.type === "function") {
return new Proxy(xOut.data, ModAPI.util.TeaVMArray_To_Recursive_BaseData_ProxyConf);
}
if (xOut && typeof xOut === "object" && !Array.isArray(xOut)) {
return new Proxy(xOut, ModAPI.util.TeaVM_to_Recursive_BaseData_ProxyConf);
}
return xOut;
}
}
return null;
}
ModAPI.array.object = function (jclass, size) {
if (Array.isArray(size)) {
return ModAPI.hooks._teavm.$rt_createArrayFromData(jclass, size);
}
return ModAPI.hooks._teavm.$rt_createArray(jclass, size);
}
ModAPI.array.boolean = function (size) {
if (Array.isArray(size)) {
return ModAPI.hooks._teavm.$rt_createBooleanArrayFromData(size);
}
return ModAPI.hooks._teavm.$rt_createBooleanArray(size);
}
ModAPI.array.byte = function (size) {
if (Array.isArray(size)) {
return ModAPI.hooks._teavm.$rt_createByteArrayFromData(size);
}
return ModAPI.hooks._teavm.$rt_createByteArray(size);
}
ModAPI.array.char = function (size) {
if (Array.isArray(size)) {
return ModAPI.hooks._teavm.$rt_createCharArrayFromData(size);
}
return ModAPI.hooks._teavm.$rt_createCharArray(size);
}
ModAPI.array.short = function (size) {
if (Array.isArray(size)) {
return ModAPI.hooks._teavm.$rt_createShortArrayFromData(size);
}
return ModAPI.hooks._teavm.$rt_createShortArray(size);
}
ModAPI.array.int = function (size) {
if (Array.isArray(size)) {
return ModAPI.hooks._teavm.$rt_createIntArrayFromData(size);
}
return ModAPI.hooks._teavm.$rt_createIntArray(size);
}
ModAPI.array.float = function (size) {
if (Array.isArray(size)) {
return ModAPI.hooks._teavm.$rt_createFloatArrayFromData(size);
}
return ModAPI.hooks._teavm.$rt_createFloatArray(size);
}
ModAPI.array.double = function (size) {
if (Array.isArray(size)) {
return ModAPI.hooks._teavm.$rt_createDoubleArrayFromData(size);
}
return ModAPI.hooks._teavm.$rt_createDoubleArray(size);
}
ModAPI.version = "v2.0";
ModAPI.flavour = "injector";
ModAPI.GNU = "terry pratchett";
ModAPI.credits = ["ZXMushroom63", "radmanplays", "OtterCodes101", "TheIdiotPlays"];
ModAPI.hooks.regenerateClassMap = function () {
ModAPI.hooks._rippedConstructorKeys = Object.keys(ModAPI.hooks._rippedConstructors);
ModAPI.hooks._rippedMethodKeys = Object.keys(ModAPI.hooks._rippedMethodTypeMap);
var compiledNames = new Set();
var metaMap = {};
//Loop through ripped metadata passes. Classes obtained through this method have full metadata (superclasses, full names, binary names, actual class)
ModAPI.hooks._rippedData.forEach(block => {
block.forEach(item => {
if (typeof item === "function") {
if (!item.$meta || typeof item.$meta.name !== "string") {
return;
}
var compiledName = ModAPI.util.getCompiledNameFromPackage(item.$meta.name);
compiledNames.add(compiledName);
metaMap[compiledName] = item;
}
});
});
ModAPI.hooks._rippedConstructorKeys.forEach(constructor => {
if (typeof constructor === "string" && constructor.length > 0) {
//Constructor names are phrased as aaa_Apple__init_3 or similar, the separator is __init_
var constructorData = constructor.split("__init_");
if (constructorData[0] && constructorData[0].includes("_")) {
compiledNames.add(constructorData[0]);
}
}
});
//Initialise all compiled names into the class map
compiledNames.forEach(compiledName => {
var item = metaMap[compiledName];
var classId = item?.$meta?.name || null;
if (!ModAPI.hooks._classMap[compiledName]) {
ModAPI.hooks._classMap[compiledName] = {
"name": compiledName.split("_")[1],
"id": classId,
"binaryName": item?.$meta?.binaryName || null,
"constructors": [],
"methods": {},
"staticMethods": {},
"staticVariables": {},
"staticVariableNames": [],
"class": item || null,
"hasMeta": !!item,
"instanceOf": function (object) {
return ModAPI.hooks._teavm.$rt_isInstance(object, item || null);
},
"compiledName": compiledName
}
}
if (typeof item?.$meta?.superclass === "function" && item?.$meta?.superclass?.$meta) {
ModAPI.hooks._classMap[compiledName].superclass = item.$meta.superclass.$meta.name;
} else {
ModAPI.hooks._classMap[compiledName].superclass = null;
}
ModAPI.hooks._classMap[compiledName].staticVariableNames = ModAPI.hooks._rippedStaticIndexer[compiledName];
ModAPI.hooks._classMap[compiledName].staticVariables = ModAPI.hooks._rippedStaticProperties[compiledName];
if (item?.["$$constructor$$"]) {
//Class does not have any hand written constructors
//Eg: class MyClass {}
ModAPI.hooks._classMap[compiledName].constructors.push(item["$$constructor$$"]);
} else {
//Class has hand written constructors, we need to search in the stash
ModAPI.hooks._rippedConstructorKeys.forEach(constructor => {
if (constructor.startsWith(compiledName + "__init_") && !constructor.includes("$lambda$")) {
ModAPI.hooks._classMap[compiledName].constructors.push(ModAPI.hooks._rippedConstructors[constructor]);
}
});
}
ModAPI.hooks._rippedMethodKeys.forEach((method) => {
if (method.startsWith(compiledName + "_") && !method.includes("$lambda$")) {
var targetMethodMap = ModAPI.hooks._classMap[compiledName].methods;
if (ModAPI.hooks._rippedMethodTypeMap[method] === "static") {
targetMethodMap = ModAPI.hooks._classMap[compiledName].staticMethods;
}
targetMethodMap[method.replace(compiledName + "_", "")] = {
method: ModAPI.hooks.methods[method],
methodName: method,
methodNameShort: method.replace(compiledName + "_", "")
};
//Prototype Injection, allows for far easier access to methods
if (typeof item === "function" && item.$meta && ModAPI.hooks._rippedMethodTypeMap[method] === "instance") {
item.prototype["$" + method.replace(compiledName + "_", "")] ||= function (...args) {
return ModAPI.hooks.methods[method].apply(this, [this, ...args]);
}
}
}
});
});
ModAPI.reflect.classes = Object.values(ModAPI.hooks._classMap);
console.log("[ModAPI] Regenerated hook classmap.");
}
ModAPI.hooks.regenerateClassMap();
ModAPI.reflect.getClassById = function (classId) {
return ModAPI.hooks._classMap[ModAPI.util.getCompiledName(classId)];
}
ModAPI.reflect.getClassByName = function (className) {
var classKeys = Object.keys(ModAPI.hooks._classMap);
var key = classKeys.filter(k => { return ModAPI.hooks._classMap[k].name === className })[0];
return key ? ModAPI.hooks._classMap[key] : null;
}
var reloadDeprecationWarnings = 0;
const TeaVM_to_BaseData_ProxyConf = {
get(target, prop, receiver) {
if (prop === "getRef") {
return function () {
return target;
}
}
if (prop === "reload") {
return function () {
if (reloadDeprecationWarnings < 10) {
console.warn("ModAPI/PluginAPI reload() is obsolete, please stop using it in code.")
reloadDeprecationWarnings++;
}
}
}
var outProp = "$" + prop;
var outputValue = Reflect.get(target, outProp, receiver);
if (outputValue && typeof outputValue === "object" && Array.isArray(outputValue.data) && typeof outputValue.type === "function") {
return outputValue.data;
}
if (outputValue && typeof outputValue === "function") {
return function (...args) {
return outputValue.apply(target, args);
}
}
return outputValue;
},
set(object, prop, value) {
var outProp = "$" + prop;
object[outProp] = value;
return true;
},
};
const TeaVMArray_To_Recursive_BaseData_ProxyConf = {
get(target, prop, receiver) {
var outputValue = Reflect.get(target, prop, receiver);
if (outputValue && typeof outputValue === "object" && !Array.isArray(outputValue)) {
return new Proxy(outputValue, TeaVM_to_Recursive_BaseData_ProxyConf);
}
if (prop === "getRef") {
return function () {
return target;
}
}
return outputValue;
},
set(object, prop, value) {
object[prop] = value;
return true;
}
}
const TeaVM_to_Recursive_BaseData_ProxyConf = {
get(target, prop, receiver) {
if (prop === "getRef") {
return function () {
return target;
}
}
if (prop === "reload") {
return function () {
if (reloadDeprecationWarnings < 10) {
console.warn("ModAPI/PluginAPI reload() is obsolete, please stop using it in code.")
reloadDeprecationWarnings++;
}
}
}
var outProp = "$" + prop;
var outputValue = Reflect.get(target, outProp, receiver);
var wrapped = ModAPI.util.wrap(outputValue, target);
if (wrapped) {
return wrapped;
}
return outputValue;
},
set(object, prop, value) {
var outProp = "$" + prop;
object[outProp] = value;
return true;
},
};
const StaticProps_ProxyConf = {
get(target, prop, receiver) {
var outProp = prop;
var outputValue = Reflect.get(target, outProp, receiver);
if (outputValue && typeof outputValue === "object" && Array.isArray(outputValue.data) && typeof outputValue.type === "function") {
return new Proxy(outputValue.data, TeaVMArray_To_Recursive_BaseData_ProxyConf);
}
if (outputValue && typeof outputValue === "object" && !Array.isArray(outputValue)) {
return new Proxy(outputValue, TeaVM_to_Recursive_BaseData_ProxyConf);
}
return outputValue;
},
set(object, prop, value) {
var outProp = prop;
object[outProp] = value;
return true;
},
};
ModAPI.util.TeaVM_to_BaseData_ProxyConf = TeaVM_to_BaseData_ProxyConf;
ModAPI.util.TeaVMArray_To_Recursive_BaseData_ProxyConf = TeaVMArray_To_Recursive_BaseData_ProxyConf;
ModAPI.util.TeaVM_to_Recursive_BaseData_ProxyConf = TeaVM_to_Recursive_BaseData_ProxyConf;
ModAPI.util.StaticProps_ProxyConf = StaticProps_ProxyConf;
ModAPI.required = new Set();
ModAPI.events = {};
ModAPI.events.types = ["event"];
ModAPI.events.lib_map = {};
ModAPI.events.listeners = { "event": [] };
ModAPI.addEventListener = function addEventListener(name, callback) {
if (name.startsWith("lib:")) {
if (ModAPI.events.lib_map[name]) {
callback({});
} else {
if (!Array.isArray(ModAPI.events.listeners[name])) {
ModAPI.events.listeners[name] = [];
}
ModAPI.events.listeners[name].push(callback);
}
console.log("[ModAPI] Added new library listener.");
return;
}
if (!callback || typeof callback !== "function") {
throw new Error("[ModAPI] Invalid callback!");
}
if (ModAPI.events.types.includes(name) || name.startsWith("custom:")) {
if (!Array.isArray(ModAPI.events.listeners[name])) {
ModAPI.events.listeners[name] = [];
}
ModAPI.events.listeners[name].push(callback);
console.log("[ModAPI] Added new event listener.");
} else {
throw new Error("[ModAPI] This event does not exist!");
}
};
ModAPI.removeEventListener = function removeEventListener(name, func, slow) {
if (!func) {
throw new Error("Invalid callback!");
}
if (!Array.isArray(ModAPI.events.listeners[name])) {
ModAPI.events.listeners[name] = [];
}
var targetArr = ModAPI.events.listeners[name];
if (!slow) {
if (targetArr.indexOf(func) !== -1) {
targetArr.splice(targetArr.indexOf(func), 1);
console.log("[ModAPI] Removed event listener.");
}
} else {
var functionString = func.toString();
targetArr.forEach((f, i) => {
if (f.toString() === functionString) {
targetArr.splice(i, 1);
console.log("[ModAPI] Removed event listener.");
}
});
}
};
ModAPI.events.newEvent = function newEvent(name, side = "unknown") {
if (!ModAPI.events.types.includes(name)) {
console.log("[ModAPI] Registered " + side + " event: " + name);
ModAPI.events.types.push(name);
}
};
ModAPI.events.callEvent = function callEvent(name, data) {
if (ModAPI.events.types.includes(name) && name.startsWith("lib:")) {
if (Array.isArray(ModAPI.events.listeners[name])) {
ModAPI.events.listeners[name].forEach((func) => {
func({});
});
}
ModAPI.events.lib_map[name] = true;
return;
}
if (
!ModAPI.events.types.includes(name) ||
!Array.isArray(ModAPI.events.listeners[name])
) {
if (!Array.isArray(ModAPI.events.listeners[name])) {
if (ModAPI.events.types.includes(name)) {
ModAPI.events.listeners.event.forEach((func) => {
func({ event: name, data: data });
});
return;
}
return;
}
console.error(
"ModAPI/PluginAPI has been called with an invalid event name: " + name
);
console.error("Please report this bug to the repo.");
return;
}
ModAPI.events.listeners[name].forEach((func) => {
func(data);
});
ModAPI.events.listeners.event.forEach((func) => {
func({ event: name, data: data });
});
};
ModAPI.events.newEvent("update", "client");
ModAPI.require = function (module) {
ModAPI.required.add(module);
};
ModAPI.onUpdate = function () {
if (ModAPI.required.has("player") && ModAPI.javaClient && ModAPI.javaClient.$thePlayer) {
ModAPI.player = new Proxy(ModAPI.javaClient.$thePlayer, TeaVM_to_Recursive_BaseData_ProxyConf);
}
if (ModAPI.required.has("network") && ModAPI.javaClient && ModAPI.javaClient.$thePlayer && ModAPI.javaClient.$thePlayer.$sendQueue) {
ModAPI.network = new Proxy(ModAPI.javaClient.$thePlayer.$sendQueue, TeaVM_to_Recursive_BaseData_ProxyConf);
}
if (ModAPI.required.has("world") && ModAPI.javaClient && ModAPI.javaClient.$theWorld) {
ModAPI.world = new Proxy(ModAPI.javaClient.$theWorld, TeaVM_to_Recursive_BaseData_ProxyConf);
}
try {
ModAPI.events.callEvent("update");
} catch (error) {
console.error(error);
}
}
ModAPI.util.stringToUint16Array = function stringToUint16Array(str) {
const buffer = new ArrayBuffer(str.length * 2); // 2 bytes for each char
const uint16Array = new Uint16Array(buffer);
for (let i = 0; i < str.length; i++) {
uint16Array[i] = str.charCodeAt(i);
}
return uint16Array;
}
//Overrides $rt_resuming, $rt_suspending, $rt_currentThread. Experimental, but should be used if call stack leaks occur as a result of running internal code.
ModAPI.freezeCallstack = function () {
ModAPI.hooks.freezeCallstack = true;
}
ModAPI.unfreezeCallstack = function () {
ModAPI.hooks.freezeCallstack = false;
}
ModAPI.util.string = ModAPI.util.str = ModAPI.hooks._teavm.$rt_str;
ModAPI.util.setStringContent = function (jclString, string) {
jclString.$characters.data = ModAPI.util.stringToUint16Array(string);
}
ModAPI.util.jclStrToJsStr = ModAPI.util.unstr = ModAPI.util.unstring = ModAPI.util.ustr = ModAPI.hooks._teavm.$rt_ustr;
ModAPI.displayToChat = function (param) {
var v = typeof param === "object" ? param.msg : (param + "");
v ||= "";
var jclString = ModAPI.util.string(v);
ModAPI.hooks.methods["nmcg_GuiNewChat_printChatMessage"](ModAPI.javaClient.$ingameGUI.$persistantChatGUI, ModAPI.hooks._classMap[ModAPI.util.getCompiledName("net.minecraft.util.ChatComponentText")].constructors[0](jclString));
}
ModAPI.util.makeArray = function makeArray(arrayClass, arrayContents = []) {
return ModAPI.hooks._teavm.$rt_createArrayFromData(arrayClass, arrayContents);
}
ModAPI.util.hashCode = function hashCode(string) {
var hash = 0;
for (var i = 0; i < string.length; i++) {
var code = string.charCodeAt(i);
hash = ((hash << 5) - hash) + code;
hash = hash & hash;
}
return Math.floor(Math.abs(hash)) + "";
};
//Check whether the thread is in a critical transition state (resuming or suspending)
//Calling functions in a critical state will cause stack implosions.
ModAPI.util.isCritical = function isCritical() {
return ModAPI.hooks._teavm.$rt_suspending() || ModAPI.hooks._teavm.$rt_resuming();
}
ModAPI.clickMouse = function () {
ModAPI.hooks.methods["nmc_Minecraft_clickMouse"](ModAPI.javaClient);
}
ModAPI.rightClickMouse = function () {
ModAPI.hooks.methods["nmc_Minecraft_rightClickMouse"](ModAPI.javaClient);
}
ModAPI.getFPS = function () {
return ModAPI.hooks.methods["nmc_Minecraft_getDebugFPS"](ModAPI.javaClient);
}
const updateMethodName = ModAPI.util.getMethodFromPackage("net.minecraft.client.entity.EntityPlayerSP", "onUpdate");
const originalUpdate = ModAPI.hooks.methods[updateMethodName];
ModAPI.hooks.methods[updateMethodName] = function (...args) {
ModAPI.onUpdate();
return originalUpdate.apply(this, args);
};
const initMethodName = ModAPI.util.getMethodFromPackage("net.minecraft.client.Minecraft", "startGame");
const originalInit = ModAPI.hooks.methods[initMethodName];
ModAPI.hooks.methods[initMethodName] = function (...args) {
var x = originalInit.apply(this, args);
//args[0] means $this (ie: minecraft instance).
ModAPI.mc = ModAPI.minecraft = new Proxy(args[0], TeaVM_to_Recursive_BaseData_ProxyConf);
globalThis.Minecraft = ModAPI.mcinstance = ModAPI.javaClient = args[0];
ModAPI.settings = new Proxy(ModAPI.mcinstance.$gameSettings, TeaVM_to_Recursive_BaseData_ProxyConf);
startModLoader();
return x;
};
var inlineIntegratedServerStartup = ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.sp.internal.ClientPlatformSingleplayer", "loadIntegratedServerSourceInline");
//Integrated server setup has a randomised suffix on the end
inlineIntegratedServerStartup = ModAPI.hooks._rippedMethodKeys.filter(key => { return key.startsWith(inlineIntegratedServerStartup); })[0];
const inlineIntegratedServerStartupMethod = ModAPI.hooks.methods[inlineIntegratedServerStartup];
ModAPI.hooks.methods[inlineIntegratedServerStartup] = function (worker, bootstrap) {
var x = inlineIntegratedServerStartupMethod.apply(this, [worker, bootstrap + ";" + globalThis.modapi_postinit + ";" + ModAPI.dedicatedServer._data.join(";")]);
ModAPI.dedicatedServer._data = [];
ModAPI.dedicatedServer._wasUsed = true;
console.log("[ModAPI] Hooked into inline integrated server.");
return x;
};
var integratedServerStartup = ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.sp.internal.ClientPlatformSingleplayer", "createBlobObj");
//Integrated server setup has a randomised suffix on the end
integratedServerStartup = ModAPI.hooks._rippedMethodKeys.filter(key => { return key.startsWith(integratedServerStartup); })[0];
const integratedServerStartupMethod = ModAPI.hooks.methods[integratedServerStartup];
ModAPI.hooks.methods[integratedServerStartup] = function (worker, bootstrap) {
var x = integratedServerStartupMethod.apply(this, [worker, bootstrap + ";" + globalThis.modapi_postinit + ";" + ModAPI.dedicatedServer._data.join(";")]);
ModAPI.dedicatedServer._data = [];
ModAPI.dedicatedServer._wasUsed = true;
console.log("[ModAPI] Hooked into external integrated server.");
return x;
};
ModAPI.events.newEvent("load", "client");
ModAPI.events.newEvent("sendchatmessage", "client");
const sendChatMessageMethodName = ModAPI.util.getMethodFromPackage("net.minecraft.client.entity.EntityPlayerSP", "sendChatMessage");
const sendChatMessage = ModAPI.hooks.methods[sendChatMessageMethodName];
ModAPI.hooks.methods[sendChatMessageMethodName] = function ($this, $message) {
var data = {
preventDefault: false,
message: ModAPI.util.jclStrToJsStr($message)
}
ModAPI.events.callEvent("sendchatmessage", data);
if (data.preventDefault) {
return;
}
if (typeof data.message === "string") {
ModAPI.util.setStringContent($message, data.message)
}
return sendChatMessage.apply(this, [$this, $message]);
}
const ScaledResolutionConstructor = ModAPI.reflect.getClassByName("ScaledResolution").constructors[0];
ModAPI.events.newEvent("frame", "client");
const frameMethodName = ModAPI.util.getMethodFromPackage("net.minecraft.client.Minecraft", "runTick");
const frameMethod = ModAPI.hooks.methods[frameMethodName];
ModAPI.hooks.methods[frameMethodName] = function (...args) {
ModAPI.events.callEvent("frame", {});
if (ModAPI.required.has("resolution") && ModAPI.mcinstance) {
ModAPI.ScaledResolution = ModAPI.resolution = new Proxy(ScaledResolutionConstructor(ModAPI.mcinstance), TeaVM_to_Recursive_BaseData_ProxyConf);
}
return frameMethod.apply(this, args);
}
ModAPI.events.newEvent("tick", "server");
const serverTickMethodName = ModAPI.util.getMethodFromPackage("net.minecraft.server.MinecraftServer", "tick");
const serverTickMethod = ModAPI.hooks.methods[serverTickMethodName];
ModAPI.hooks.methods[serverTickMethodName] = function ($this) {
if (ModAPI.util.isCritical()) {
return serverTickMethod.apply(this, [$this]);
}
var data = { preventDefault: false }
ModAPI.events.callEvent("tick", data);
if (data.preventDefault) {
return;
}
return serverTickMethod.apply(this, [$this]);
}
ModAPI.events.newEvent("serverstart", "server");
const serverStartMethodName = ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.sp.server.EaglerMinecraftServer", "startServer");
const serverStartMethod = ModAPI.hooks.methods[serverStartMethodName];
ModAPI.hooks.methods[serverStartMethodName] = function ($this) {
var x = serverStartMethod.apply(this, [$this]);
ModAPI.server = new Proxy($this, ModAPI.util.TeaVM_to_Recursive_BaseData_ProxyConf);
ModAPI.rawServer = $this;
ModAPI.events.callEvent("serverstart", {});
return x;
}
ModAPI.events.newEvent("serverstop", "server");
const serverStopMethodName = ModAPI.util.getMethodFromPackage("net.minecraft.server.MinecraftServer", "stopServer");
const serverStopMethod = ModAPI.hooks.methods[serverStopMethodName];
ModAPI.hooks.methods[serverStopMethodName] = function ($this) {
var x = serverStopMethod.apply(this, [$this]);
ModAPI.server = ModAPI.serverInstance = null;
ModAPI.events.callEvent("serverstop", {});
return x;
}
ModAPI.events.newEvent("receivechatmessage", "server");
const receiveChatMessageMethodName = ModAPI.util.getMethodFromPackage("net.minecraft.network.play.client.C01PacketChatMessage", "processPacket");
const receiveChatMessageMethod = ModAPI.hooks.methods[receiveChatMessageMethodName];
ModAPI.hooks.methods[receiveChatMessageMethodName] = function (...args) {
var $this = args[0];
var data = {
preventDefault: false,
message: ModAPI.util.jclStrToJsStr($this.$message3)
}
ModAPI.events.callEvent("sendchatmessage", data);
if (data.preventDefault) {
return;
}
if (typeof data.message === "string") {
ModAPI.util.setStringContent($this.$message3, data.message)
}
var x = receiveChatMessageMethod.apply(this, args);
return x;
}
ModAPI.events.newEvent("processcommand", "server");
const processCommandMethodName = ModAPI.util.getMethodFromPackage("net.minecraft.command.CommandHandler", "executeCommand");
const processCommandMethod = ModAPI.hooks.methods[processCommandMethodName];
ModAPI.hooks.methods[processCommandMethodName] = function ($this, $sender, $rawCommand) {
var data = {
preventDefault: false,
sender: new Proxy($sender, TeaVM_to_Recursive_BaseData_ProxyConf),
command: ModAPI.util.jclStrToJsStr($rawCommand)
}
ModAPI.events.callEvent("processcommand", data);
if (data.preventDefault) {
return 0;
}
if (typeof data.command === "string") {
ModAPI.util.setStringContent($rawCommand, data.command)
}
var x = processCommandMethod.apply(this, [$this, $sender, $rawCommand]);
return x;
}
ModAPI.items = new Proxy(ModAPI.hooks._classMap[ModAPI.util.getCompiledName("net.minecraft.init.Items")].staticVariables, StaticProps_ProxyConf);
ModAPI.blocks = new Proxy(ModAPI.hooks._classMap[ModAPI.util.getCompiledName("net.minecraft.init.Blocks")].staticVariables, StaticProps_ProxyConf);
ModAPI.materials = new Proxy(ModAPI.hooks._classMap[ModAPI.util.getCompiledName("net.minecraft.block.material.Material")].staticVariables, StaticProps_ProxyConf);
ModAPI.enchantments = new Proxy(ModAPI.hooks._classMap[ModAPI.util.getCompiledName("net.minecraft.enchantment.Enchantment")].staticVariables, StaticProps_ProxyConf);
const originalOptionsInit = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.gui.GuiOptions", "initGui")];
ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.gui.GuiOptions", "initGui")] = function (...args) {
var x = originalOptionsInit.apply(this, args);
//NOT A BUG DO NOT FIX
var msg = Math.random() < 0.025 ? "Plugins" : "Mods";
// Find the right constructor. (int id, int x, int y, int width, int height, String buttonText);
var btnConstructor = ModAPI.hooks._classMap['nmcg_GuiButton'].constructors.filter(c => { return c.length === 6 })[0];
var btn = btnConstructor(9635329, 0, args[0].$height8 - 21, 100, 20, ModAPI.util.str(msg));
args[0].$buttonList.$add(btn);
return x;
}
const originalOptionsAction = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.gui.GuiOptions", "actionPerformed")];
ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.gui.GuiOptions", "actionPerformed")] = function (...args) {
if (args[1] && args[1].$id12 === 9635329) {
if (typeof window.modapi_displayModGui === "function") {
window.modapi_displayModGui();
} else {
alert("[ModAPI] Mod Manager GUI does not exist!")
}
}
var x = originalOptionsAction.apply(this, args);
return x;
}
const originalCrashMethod = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.internal.teavm.ClientMain", "showCrashScreen")];
ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.internal.teavm.ClientMain", "showCrashScreen")] = function (...args) {
if (window.confirm("Your game has crashed, do you want to open the mod manager gui?")) {
return modapi_displayModGui();
};
var x = originalCrashMethod.apply(this, args);
return x;
}
var inited = false;
const originalMainMethod = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.internal.teavm.ClientMain", "_main")];
ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.internal.teavm.ClientMain", "_main")] = function (...args) {
if (!inited) {
inited = true;
return modapi_displayModGui(globalThis.main);
} else {
return originalMainMethod.apply(this, args);
}
}
})();`;

View File

@ -1,4 +1,4 @@
(() => { globalThis.modapi_postinit = "(" + (() => {
//EaglerForge post initialization code. //EaglerForge post initialization code.
//This script cannot contain backticks, escape characters, or backslashes in order to inject into the dedicated server code. //This script cannot contain backticks, escape characters, or backslashes in order to inject into the dedicated server code.
var startedModLoader = false; var startedModLoader = false;
@ -787,4 +787,4 @@
return originalMainMethod.apply(this, args); return originalMainMethod.apply(this, args);
} }
} }
})(); }).toString() + ")();";

View File

@ -13,7 +13,7 @@
Add makeItemStack to LCI [done] Add makeItemStack to LCI [done]
Fix blocklook.js [todo] Fix blocklook.js [todo]
Fix setblocktest.js [done] Fix setblocktest.js [done]
Add custom ModAPI thread class to stop stack implosions [todo] `ModAPI.asyncMode(10)# and `ModAPI.exitAsync()# to allow using `await# on things. I will have to create a new `TeaVMThread#, and create a `ModAPI.util.async(fn)# method to promisify. [wip]
Full backwards compatibility for mods [todo] Full backwards compatibility for mods [todo]
</div> </div>

View File

@ -43,12 +43,17 @@ span summary span {
right: -4px; right: -4px;
background-color: red; background-color: red;
border-radius: 9px; border-radius: 9px;
opacity: 0.6;
transition: opacity 0.2s;
}
span summary span:hover {
opacity: 1;
} }
span.working summary span { span.working summary span {
background-color: rgb(255, 102, 0); background-color: rgb(255, 102, 0);
} }
span.done summary span { span.done summary span {
background-color: greenyellow; background-color: lime;
} }
#todolist { #todolist {
width: calc(50vw - 0.6rem); width: calc(50vw - 0.6rem);

View File

@ -23,7 +23,7 @@ list.forEach(a => {
var d = document.createElement("span"); var d = document.createElement("span");
var s = document.createElement("summary"); var s = document.createElement("summary");
d.appendChild(s); d.appendChild(s);
s.innerText = x[0].trim(); s.innerHTML = x[0].trim().replaceAll("`", "<code>").replaceAll('#', "</code>");
var z = document.createElement("span"); var z = document.createElement("span");
d.classList.add(map[x[1].toLowerCase().trim()] || "todo"); d.classList.add(map[x[1].toLowerCase().trim()] || "todo");
s.appendChild(z); s.appendChild(z);