diff --git a/build_injector.sh b/build_injector.sh
index 6adb4bc..a8985f7 100755
--- a/build_injector.sh
+++ b/build_injector.sh
@@ -1,9 +1,9 @@
#!/bin/sh
-BASE64BUNDLE="return '$(base64 dist/index.js)'"
+BASE64BUNDLE="return '$(base64 < dist/index.js)'"
VERSION="$(grep version package.json | sed -e 's/..version....//g' -e 's/...$//g')"
COMMITHASH="$(git rev-parse --short HEAD)"
-echo -n $BASE64BUNDLE > bundle.b64
+printf "%s" "$BASE64BUNDLE" > bundle.b64
sed -e "s/WISPCRAFTVERSION/$VERSION/g" -e "s/WISPCRAFTCOMMITHASH/$COMMITHASH/g" -e "/WISPCRAFTSRCBUNDLE/r bundle.b64" -e "/WISPCRAFTSRCBUNDLE/d" "index.html" > dist/injector.html
rm bundle.b64
echo "Injector Build Complete!"
diff --git a/ci/_headers b/ci/_headers
deleted file mode 100644
index 2e9e5ca..0000000
--- a/ci/_headers
+++ /dev/null
@@ -1,2 +0,0 @@
-https://mc.hgci.org/*
- Origin-Trial: Aqcno6C8l6XJXPSejAWrhNF3vUz282YNEPS/Jy4P39PQjrZ0AyxkJjJp97jqSYL7RR2cSbplUVtk7FES3f2+8wUAAABkeyJvcmlnaW4iOiJodHRwczovL21jLmhnY2kub3JnOjQ0MyIsImZlYXR1cmUiOiJXZWJBc3NlbWJseUpTUHJvbWlzZUludGVncmF0aW9uIiwiZXhwaXJ5IjoxNzQ0Njc1MjAwfQ==
\ No newline at end of file
diff --git a/index.html b/index.html
index f83ad68..67b7ee4 100644
--- a/index.html
+++ b/index.html
@@ -163,7 +163,7 @@
Wispcraft Injector
- Inject WISP into your EaglercraftX 1.8 client and play on real Minecraft
+ Inject WISP into your EaglercraftX 1.8/1.12 client and play on real Minecraft
servers!
diff --git a/setup.sh b/setup.sh
index 2f82f5a..9057a3e 100755
--- a/setup.sh
+++ b/setup.sh
@@ -1,11 +1,17 @@
#!/bin/sh
-
+rm -rf dist
+mkdir -p dist
npm run build
-
cd dist
-curl -L https://bafybeialxd4xd7puchx555zqrfmuaz7e7n7nh4crb3sfhvprxedx4c64fq.ipfs.dweb.link/?filename=EaglercraftX_1.8_WASM-GC_Offline_Download.zip -o eaglercraft.zip
-unzip eaglercraft.zip
-rm eaglercraft.zip
-mv EaglercraftX_1.8_WASM-GC_Offline_Download.html index.html
-sed -i 's//\|' index.html
+else
+ sed -i '' 's|||' index.html
+fi
diff --git a/src/auth.ts b/src/auth.ts
index b372f1e..311f192 100644
--- a/src/auth.ts
+++ b/src/auth.ts
@@ -1,3 +1,4 @@
+import encodeQR from "qr";
import { epoxyFetch } from "./connection/epoxy";
// https://gist.github.com/Plagiatus/ce5f18bc010395fc45d8553905e10f55
@@ -29,6 +30,30 @@ interface OAuthResponse {
refresh_token: string;
}
+interface DeviceCodeResponse {
+ device_code: string;
+ user_code: string;
+ verification_uri: string;
+ expires_in: number;
+ interval: number;
+}
+
+type AuthCodeResponse = DeviceCodeResponse & {
+ link_url: string;
+ qr_svg: string;
+ qr_svg_uri: string;
+};
+
+export async function getAuthCodeResponse () {
+ const codeGenerator = await deviceCodeAuth();
+ const linkUrl = "https://microsoft.com/link?otc=" + codeGenerator.code;
+ const qrSvg = encodeQR(linkUrl, "svg", {
+ scale: 6,
+ border: 1,
+ });
+ return { ...codeGenerator, link_url: linkUrl, qr_svg: qrSvg, qr_svg_uri: `data:image/svg+xml;base64,${btoa(qrSvg)}` }
+}
+
export async function deviceCodeAuth() {
// TOOD: Type
const deviceCodeRes = await epoxyFetch(
@@ -45,14 +70,6 @@ export async function deviceCodeAuth() {
}
);
- interface DeviceCodeResponse {
- device_code: string;
- user_code: string;
- verification_uri: string;
- expires_in: number;
- interval: number;
- }
-
const deviceCodeData: DeviceCodeResponse = await deviceCodeRes.json();
const { device_code, user_code, verification_uri, interval } = deviceCodeData;
diff --git a/src/buffer.ts b/src/buffer.ts
index 955aa32..095ab9a 100644
--- a/src/buffer.ts
+++ b/src/buffer.ts
@@ -71,6 +71,16 @@ export class Buffer {
this.extend(new Buffer(data));
}
+ readUByte(): number {
+ const ret = this.get(0);
+ this.take(1);
+ return ret;
+ }
+
+ writeUByte(num: number) {
+ this.extend(new Buffer([num & 0xff]));
+ }
+
readUShort(): number {
const ret = (this.get(0) << 8) | this.get(1);
this.take(2);
diff --git a/src/connection/fakewebsocket.ts b/src/connection/fakewebsocket.ts
index 5b00983..0bd7c72 100644
--- a/src/connection/fakewebsocket.ts
+++ b/src/connection/fakewebsocket.ts
@@ -8,8 +8,8 @@ import { authstore, COMMITHASH, VERSION, wispUrl } from "..";
import { Buffer } from "../buffer";
import { showUI } from "../ui";
import { epoxyWs } from "./epoxy";
-// @ts-ignore typescript sucks
-import wispcraft from "./wispcraft.png";
+// @ts-ignore
+import workshop from "../img/workshop64.png";
class WispWS extends EventTarget {
inner: Connection;
@@ -132,7 +132,7 @@ class SettingsWS extends EventTarget {
}),
})
);
- fetch(wispcraft)
+ fetch(workshop)
.then((response) => response.blob())
.then((blob) => createImageBitmap(blob))
.then((image) => {
diff --git a/src/connection/index.ts b/src/connection/index.ts
index c075a5e..b8e8885 100644
--- a/src/connection/index.ts
+++ b/src/connection/index.ts
@@ -1,4 +1,4 @@
-import { EaglerProxy } from "../1.8";
+import { EaglerProxy } from "../proxy";
import { connect_tcp } from "./epoxy";
import { epoxyFetch } from "./epoxy";
import { Buffer } from "../buffer";
diff --git a/src/connection/wispcraft.png b/src/connection/wispcraft.png
deleted file mode 100644
index 497d46e..0000000
Binary files a/src/connection/wispcraft.png and /dev/null differ
diff --git a/src/img/workshop.png b/src/img/workshop.png
new file mode 100644
index 0000000..82fa6db
Binary files /dev/null and b/src/img/workshop.png differ
diff --git a/src/img/workshop64.png b/src/img/workshop64.png
new file mode 100644
index 0000000..c1838bb
Binary files /dev/null and b/src/img/workshop64.png differ
diff --git a/src/index.ts b/src/index.ts
index dcd1cfa..3bb6343 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,12 +1,15 @@
-import { getProfile, minecraftAuth, UserInfo } from "./auth";
+import { getAuthCodeResponse, getProfile, minecraftAuth, UserInfo } from "./auth";
import { epoxyFetch, initWisp } from "./connection/epoxy";
import { makeFakeWebSocket } from "./connection/fakewebsocket";
+import { showUI } from "./ui";
//@ts-expect-error this gets filled in by rollup
export const VERSION = self.VERSION;
//@ts-expect-error this too
export const COMMITHASH = self.COMMITHASH;
+export const DEFAULT_WISP_URL = "wss://anura.pro/";
+
export let wispUrl: string;
export type AuthStore = {
@@ -41,7 +44,7 @@ wispUrl =
((window as any).anura && (window as any).anura.wsproxyURL) ||
new URL(window.location.href).searchParams.get("wisp") ||
localStorage["wispcraft_wispurl"] ||
- "wss://anura.pro/";
+ DEFAULT_WISP_URL;
try {
setWispUrl(wispUrl);
@@ -49,14 +52,27 @@ try {
console.error(e);
}
+export function getLoggedInAccounts (): TokenStore[] | undefined {
+ const accounts = localStorage["wispcraft_accounts"];
+ if (accounts) {
+ return JSON.parse(accounts) as TokenStore[];
+ } else {
+ return undefined;
+ }
+}
+
+export function getLastUsedAccount (): TokenStore | undefined {
+ const username = localStorage["wispcraft_last_used_account"];
+ const accounts = getLoggedInAccounts();
+ if (username && accounts) {
+ return accounts.find((account) => account.username === username);
+ } else {
+ return undefined;
+ }
+}
+
if (localStorage["wispcraft_accounts"]) {
- const accounts = JSON.parse(
- localStorage["wispcraft_accounts"]
- ) as TokenStore[];
- const account = accounts.find(
- (account) =>
- account.username === localStorage["wispcraft_last_used_account"]
- );
+ const account = getLastUsedAccount();
if (account) {
(async () => {
try {
@@ -71,6 +87,9 @@ if (localStorage["wispcraft_accounts"]) {
}
}
+export const showSettingsUI = showUI;
+export const getAuthCode = getAuthCodeResponse;
+
// replace websocket with our own
window.WebSocket = makeFakeWebSocket();
diff --git a/src/1.8.ts b/src/proxy.ts
similarity index 85%
rename from src/1.8.ts
rename to src/proxy.ts
index f9c9426..da6bfb7 100644
--- a/src/1.8.ts
+++ b/src/proxy.ts
@@ -16,12 +16,16 @@ import { joinServer } from "./auth";
import { VERSION, type AuthStore } from ".";
// import { authstore } from "./index";
-// https://minecraft.wiki/w/Protocol?oldid=2772100
enum State {
- Handshaking = 0x0,
- Status = 0x1,
- Login = 0x2,
- Play = 0x3,
+ Handshaking = 0x00,
+ Status = 0x01,
+ Login = 0x02,
+ Play = 0x03,
+}
+
+enum PacketType {
+ Clientbound,
+ Serverbound
}
// EAG_ prefixed are nonstandard
@@ -39,9 +43,17 @@ enum Serverbound {
LoginStart = 0x00,
EncryptionResponse = 0x01,
/* ==PLAY== */
+ PluginMessage = -0x00,
+}
+
+enum Serverbound_1_8 {
PluginMessage = 0x17,
}
+enum Serverbound_1_12 {
+ PluginMessage = 0x09,
+}
+
enum Clientbound {
/* ==HANDSHAKING== */
EAG_ServerVersion = 0x02,
@@ -58,10 +70,16 @@ enum Clientbound {
SetCompression = 0x03,
/* ==PLAY== */
SetCompressionPlay = 0x46,
+ PluginMessage = -0x00,
+}
+
+enum Clientbound_1_8 {
PluginMessage = 0x3f,
}
-const MINECRAFT_PROTOCOL_VERSION = 47;
+enum Clientbound_1_12 {
+ PluginMessage = 0x18,
+}
class Packet extends Buffer {
constructor(packetType: number) {
@@ -99,6 +117,7 @@ const colorMap: { [key: string]: string } = {
yellow: "e",
white: "f",
};
+
function chatToLegacyString(chat: ChatSchema) {
let special = "ยง";
let str = "";
@@ -125,6 +144,34 @@ function createEagKick(reason: string): Buffer {
return eag;
}
+function createProtocolArray(pvn: number): number[] {
+ if (pvn < 256) {
+ return [0, pvn];
+ } else {
+ return [(pvn >> 8) & 0xff, pvn & 0xff];
+ }
+}
+
+function getVersionPacketId(protocol: number, type: PacketType, packet: string): number {
+ if (type == PacketType.Serverbound) {
+ if (protocol == 47) {
+ return Serverbound_1_8[packet];
+ } else if (protocol == 340) {
+ return Serverbound_1_12[packet];
+ } else {
+ return Serverbound[packet]
+ }
+ } else {
+ if (protocol == 47) {
+ return Clientbound_1_8[packet];
+ } else if (protocol == 340) {
+ return Clientbound_1_12[packet];
+ } else {
+ return Clientbound[packet]
+ }
+ }
+}
+
export class EaglerProxy {
loggedIn: boolean = false;
handshook: boolean = false;
@@ -142,6 +189,8 @@ export class EaglerProxy {
offlineUuid: string = "";
isPremium: boolean = false;
+ protocol: number = -1;
+
constructor(
eaglerOut: BytesWriter,
epoxyOut: BytesWriter,
@@ -160,14 +209,20 @@ export class EaglerProxy {
case State.Handshaking:
switch (packet.readVarInt()) {
case Serverbound.EAG_ClientVersion:
+ packet.readUByte();
+ const l = packet.readUShort();
+ for (let i = 0; i < l; i++) {
+ packet.readUShort();
+ }
+ packet.readUShort()
+ this.protocol = packet.readUShort();
const fakever = new Packet(Clientbound.EAG_ServerVersion);
{
const brand = new TextEncoder().encode("Wispcraft");
fakever.writeBytes([
0,
3,
- 0,
- MINECRAFT_PROTOCOL_VERSION,
+ ...createProtocolArray(this.protocol),
brand.length,
]);
fakever.extend(new Buffer(brand));
@@ -199,7 +254,7 @@ export class EaglerProxy {
this.state = State.Login;
let handshake = new Packet(Serverbound.Handshake);
- handshake.writeVarInt(MINECRAFT_PROTOCOL_VERSION);
+ handshake.writeVarInt(this.protocol);
handshake.writeString(this.serverAddress);
handshake.writeUShort(this.serverPort);
handshake.writeVarInt(State.Login);
@@ -222,7 +277,7 @@ export class EaglerProxy {
case State.Play:
let pk = packet.readVarInt(false)!;
switch (pk) {
- case Serverbound.PluginMessage:
+ case getVersionPacketId(this.protocol, PacketType.Serverbound, "PluginMessage"):
let fard = packet.copy();
fard.readVarInt();
let tag = fard.readString();
@@ -234,7 +289,7 @@ export class EaglerProxy {
if (buf.length == 0) {
return;
}
- let resp = new Packet(Clientbound.PluginMessage);
+ let resp = new Packet(getVersionPacketId(this.protocol, PacketType.Clientbound, "PluginMessage"));
resp.writeString(tag);
resp.extend(buf);
this.eagler.write(resp);
@@ -393,12 +448,14 @@ export class EaglerProxy {
case State.Play:
switch (packet.readVarInt(false)) {
case Clientbound.SetCompressionPlay:
- packet.readVarInt();
- let threshold = packet.readVarInt();
- this.decompressor.compressionThresh = threshold;
- this.compressor.compressionThresh = threshold;
- break;
- case Clientbound.PluginMessage:
+ if (this.protocol == 47) {
+ packet.readVarInt();
+ let threshold = packet.readVarInt();
+ this.decompressor.compressionThresh = threshold;
+ this.compressor.compressionThresh = threshold;
+ break;
+ }
+ case getVersionPacketId(this.protocol, PacketType.Clientbound, "PluginMessage"):
let pk = packet.copy();
pk.readVarInt();
let tag = pk.readString();
@@ -415,7 +472,7 @@ export class EaglerProxy {
// pings remote server, sends json to eagler
async ping() {
let handshake = new Packet(Serverbound.Handshake);
- handshake.writeVarInt(MINECRAFT_PROTOCOL_VERSION);
+ handshake.writeVarInt(47);
handshake.writeString(this.serverAddress);
handshake.writeUShort(this.serverPort);
handshake.writeVarInt(State.Status);
diff --git a/src/ui.ts b/src/ui.ts
index 1f230d7..3688350 100644
--- a/src/ui.ts
+++ b/src/ui.ts
@@ -1,7 +1,9 @@
-import { deviceCodeAuth, getProfile, minecraftAuth } from "./auth";
+import { deviceCodeAuth, getAuthCodeResponse, getProfile, minecraftAuth } from "./auth";
import { reconnect, set_wisp_server } from "./connection/epoxy";
-import { authstore, TokenStore } from ".";
+import { authstore, DEFAULT_WISP_URL, getLastUsedAccount, getLoggedInAccounts, TokenStore, wispUrl } from ".";
import encodeQR from "qr";
+// @ts-ignore
+import workshop from "./img/workshop.png";
let keydownListeners: Array = [];
const nativeAddEventListener = window.addEventListener;
@@ -212,7 +214,13 @@ export function createUI() {
outline: none;
}
- .settings-ui .button {
+ .settings-ui .action {
+ display: flex;
+ flex-direction: row;
+ gap: 0.5rem;
+ }
+
+ .settings-ui .action .button {
background-color: #3C82F6;
color: #0F172A;
border: 1px solid #1E293B;
@@ -225,13 +233,14 @@ export function createUI() {
display: inline-flex;
align-items: center;
justify-content: center;
+ flex: 1;
}
- .settings-ui .button:hover {
+ .settings-ui .action .button:hover {
background-color: rgba(57 128 242, 0.9);
}
- .settings-ui .button:focus {
+ .settings-ui .action .button:focus {
outline: none;
}
@@ -253,7 +262,6 @@ export function createUI() {
width: 148px;
height: 148px;
transition: all 0.2s ease;
- cursor: none;
}
.settings-ui #account_status svg:hover {
@@ -291,7 +299,7 @@ export function createUI() {