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.