/** * Downloads a file from a url and writes it to the CheerpJ filesystem. * @param {string} url * @param {string} destPath * @param {(downloadedBytes: number, totalBytes: number) => void} [progressCallback] * @returns {Promise} */ async function downloadFileToCheerpJ(url, destPath, progressCallback) { const response = await fetch(url); const reader = response.body.getReader(); const contentLength = +response.headers.get('Content-Length'); const bytes = new Uint8Array(contentLength); progressCallback?.(0, contentLength); let pos = 0; while (true) { const { done, value } = await reader.read(); if (done) break; bytes.set(value, pos); pos += value.length; progressCallback?.(pos, contentLength); } // Write to CheerpJ filesystem return new Promise((resolve, reject) => { cheerpOSOpen(cjFDs, destPath, "w", fd => { cheerpOSWrite(cjFDs, fd, bytes, 0, bytes.length, w => { cheerpOSClose(cjFDs, fd); resolve(); }); }); }); } const template = document.createElement('template'); template.innerHTML = `

This is a proof-of-concept demo of Minecraft 1.2.5 running unmodified in the browser.

Clicking the button below will download the client from mojang.com. By clicking it, you agree to the Minecraft EULA.

This is not an official Minecraft product. It is not approved by or associated with Mojang or Microsoft.
`; export default class MinecraftClient extends HTMLElement { #canvas; #progress; #button; #display; #intro; #isRunning; constructor() { super(); const shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.appendChild(template.content.cloneNode(true)); this.#button = shadowRoot.querySelector('button'); this.#button.addEventListener('click', () => this.run()); this.#canvas = shadowRoot.querySelector('canvas'); this.#canvas.width = 854; this.#canvas.height = 480; this.#canvas.tabIndex = -1; this.#canvas.style.display = 'none'; this.#progress = shadowRoot.querySelector('progress'); this.#progress.style.display = 'none'; this.#intro = shadowRoot.querySelector('.intro'); // CheerpJ needs an element to render to, but we are going to render to own canvas this.#display = shadowRoot.querySelector('.display'); this.#display.setAttribute('style', 'width:100%;height:100%;position:absolute;top:0;left:0px;visibility:hidden;'); cheerpjCreateDisplay(-1, -1, this.#display); this.#isRunning = false; } static register() { customElements.define('minecraft-client', this); } /** @returns {Promise} Exit code */ async run() { if (this.#isRunning) { throw new Error('Already running'); } this.#intro.style.display = 'none'; this.#progress.style.display = 'unset'; const jarPath = "./assets/g/125/client.jar" await downloadFileToCheerpJ( "./assets/g/125/client.jar", jarPath, (downloadedBytes, totalBytes) => { this.#progress.value = downloadedBytes; this.#progress.max = totalBytes; } ); this.#progress.style.display = 'none'; this.#canvas.style.display = 'unset'; window.lwjglCanvasElement = this.#canvas; const exitCode = await cheerpjRunMain("net.minecraft.client.Minecraft", `/app/lwjgl-2.9.0.jar:/app/lwjgl_util-2.9.0.jar:${jarPath}`) this.#canvas.style.display = 'none'; this.#isRunning = false; return exitCode; } /** @returns {boolean} */ get isRunning() { return this.#isRunning; } }