diff --git a/livesplit-core b/livesplit-core index 615fa72..bf50b6f 160000 --- a/livesplit-core +++ b/livesplit-core @@ -1 +1 @@ -Subproject commit 615fa72e3f9fa8a7b5e3494d4c5f7ab68c1e7eaf +Subproject commit bf50b6fd5eb0be154e8e827fee5eced0c0b47d7f diff --git a/src/index.tsx b/src/index.tsx index fc669b9..a798d54 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -10,22 +10,29 @@ import "./css/font-awesome.css"; import "./css/livesplit.css"; async function run() { - await load("src/livesplit_core.wasm"); + try { + await load("src/livesplit_core.wasm"); - ReactDOM.render( -
- - -
, - document.getElementById("base"), - ); + ReactDOM.render( +
+ + +
, + document.getElementById("base"), + ); + } catch (_) { + alert(`Couldn't load LiveSplit One. \ +You may be using a browser that doesn't support WebAssembly. \ +Alternatively, you may be using an Adblocker like uBlock Origin. \ +Those are known to block WebAssembly.`); + } } run(); diff --git a/src/livesplit.ts b/src/livesplit.ts index b0af9f0..342c62d 100644 --- a/src/livesplit.ts +++ b/src/livesplit.ts @@ -29,15 +29,14 @@ declare namespace WebAssembly { function instantiate(bufferSource: ArrayBuffer | Uint8Array, importObject?: any): Promise; } -declare class TextEncoder -{ + +declare class TextEncoder { constructor(label?: string, options?: TextEncoding.TextEncoderOptions); encoding: string; encode(input?: string, options?: TextEncoding.TextEncodeOptions): Uint8Array; } -declare class TextDecoder -{ +declare class TextDecoder { constructor(utfLabel?: string, options?: TextEncoding.TextDecoderOptions) encoding: string; fatal: boolean; @@ -116,8 +115,69 @@ export async function load(path?: string) { }); }; -const utf8Encoder = new TextEncoder("UTF-8"); -const utf8Decoder = new TextDecoder("UTF-8"); +let encodeUtf8: (str: string) => Uint8Array; +if (!(global as any)["TextEncoder"]) { + encodeUtf8 = (str) => { + var utf8 = []; + for (var i = 0; i < str.length; i++) { + var charcode = str.charCodeAt(i); + if (charcode < 0x80) { + utf8.push(charcode); + } else if (charcode < 0x800) { + utf8.push(0xc0 | (charcode >> 6), + 0x80 | (charcode & 0x3f)); + } else if (charcode < 0xd800 || charcode >= 0xe000) { + utf8.push(0xe0 | (charcode >> 12), + 0x80 | ((charcode >> 6) & 0x3f), + 0x80 | (charcode & 0x3f)); + } else { + i++; + charcode = 0x10000 + (((charcode & 0x3ff) << 10) + | (str.charCodeAt(i) & 0x3ff)) + utf8.push(0xf0 | (charcode >> 18), + 0x80 | ((charcode >> 12) & 0x3f), + 0x80 | ((charcode >> 6) & 0x3f), + 0x80 | (charcode & 0x3f)); + } + } + return new Uint8Array(utf8); + }; +} else { + const encoder = new TextEncoder("UTF-8"); + encodeUtf8 = (str) => encoder.encode(str); +} + +let decodeUtf8: (data: Uint8Array) => string; +if (!(global as any)["TextDecoder"]) { + decodeUtf8 = (data) => { + var str = '', + i; + + for (i = 0; i < data.length; i++) { + var value = data[i]; + + if (value < 0x80) { + str += String.fromCharCode(value); + } else if (value > 0xBF && value < 0xE0) { + str += String.fromCharCode((value & 0x1F) << 6 | data[i + 1] & 0x3F); + i += 1; + } else if (value > 0xDF && value < 0xF0) { + str += String.fromCharCode((value & 0x0F) << 12 | (data[i + 1] & 0x3F) << 6 | data[i + 2] & 0x3F); + i += 2; + } else { + var charCode = ((value & 0x07) << 18 | (data[i + 1] & 0x3F) << 12 | (data[i + 2] & 0x3F) << 6 | data[i + 3] & 0x3F) - 0x010000; + + str += String.fromCharCode(charCode >> 10 | 0xD800, charCode & 0x03FF | 0xDC00); + i += 3; + } + } + + return str; + }; +} else { + const decoder = new TextDecoder("UTF-8"); + decodeUtf8 = (data) => decoder.decode(data); +} interface Slice { ptr: number, @@ -135,7 +195,7 @@ function allocInt8Array(src: Int8Array): Slice { } function allocString(str: string): Slice { - const stringBuffer = utf8Encoder.encode(str); + const stringBuffer = encodeUtf8(str); const len = stringBuffer.length + 1; const ptr = instance().exports.alloc(len); const slice = new Uint8Array(instance().exports.memory.buffer, ptr, len); @@ -153,7 +213,7 @@ function decodeString(ptr: number): string { end += 1; } const slice = memory.slice(ptr, end); - return utf8Decoder.decode(slice); + return decodeUtf8(slice); } function dealloc(slice: Slice) { @@ -2818,7 +2878,7 @@ export class LayoutEditorRefMut extends LayoutEditorRef { /** * Sets a setting's value of the selected component by its setting index * to the given value. - * + * * This panics if the type of the value to be set is not compatible with * the type of the setting's value. A panic can also occur if the index of * the setting provided is out of bounds. @@ -2836,7 +2896,7 @@ export class LayoutEditorRefMut extends LayoutEditorRef { /** * Sets a setting's value of the general settings by its setting index to * the given value. - * + * * This panics if the type of the value to be set is not compatible with * the type of the setting's value. A panic can also occur if the index of * the setting provided is out of bounds. @@ -3480,9 +3540,9 @@ export class RunRef { /** * Returns a file name (without the extension) suitable for this Run that * is built the following way: - * + * * Game Name - Category Name - * + * * If either is empty, the dash is omitted. Special characters that cause * problems in file names are also omitted. If an extended category name is * used, the variables of the category are appended in a parenthesis. @@ -3496,9 +3556,9 @@ export class RunRef { } /** * Returns a name suitable for this Run that is built the following way: - * + * * Game Name - Category Name - * + * * If either is empty, the dash is omitted. If an extended category name is * used, the variables of the category are appended in a parenthesis. */ @@ -3513,7 +3573,7 @@ export class RunRef { * Returns an extended category name that possibly includes the region, * platform and variables, depending on the arguments provided. An extended * category name may look like this: - * + * * Any% (No Tuner, JPN, Wii Emulator) */ extendedCategoryName(showRegion: boolean, showPlatform: boolean, showVariables: boolean): string { @@ -3830,7 +3890,7 @@ export class RunEditorRefMut extends RunEditorRef { * In addition to the segments that are already selected, the segment with * the given index is being selected. The segment chosen also becomes the * active segment. - * + * * This panics if the index of the segment provided is out of bounds. */ selectAdditionally(index: number) { @@ -3842,7 +3902,7 @@ export class RunEditorRefMut extends RunEditorRef { /** * Selects the segment with the given index. All other segments are * unselected. The segment chosen also becomes the active segment. - * + * * This panics if the index of the segment provided is out of bounds. */ selectOnly(index: number) { @@ -6511,9 +6571,9 @@ export class TimerRefMut extends TimerRef { * attempt is paused, it also resumes that attempt. Additionally, if the * attempt is finished, the final split time is adjusted to not include the * pause times as well. - * + * * # Warning - * + * * This behavior is not entirely optimal, as generally only the final split * time is modified, while all other split times are left unmodified, which * may not be what actually happened during the run.