Skip to content
43 changes: 18 additions & 25 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"dependencies": {
"@katana-project/zip": "^0.7.1",
"@monaco-editor/react": "^4.7.0",
"@run-slicer/vf": "^0.5.0-1.11.2",
"vf-1.11.2": "npm:@run-slicer/vf@^0.5.0-1.11.2",
"vf-1.12.0": "npm:@run-slicer/vf@^0.6.1-1.12.0",
"@xyflow/react": "^12.10.1",
"antd": "^6.3.2",
"comlink": "^4.4.2",
Expand Down
6 changes: 3 additions & 3 deletions src/logic/Decompiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {
combineLatest, distinctUntilChanged, from, map, Observable, of, shareReplay, switchMap, tap, throttleTime
} from "rxjs";
import { minecraftJar, type MinecraftJar } from "./MinecraftApi";
import { selectedFile } from "./State";
import { selectedFile, vineflowerVersion } from "./State";
import { bytecode, displayLambdas } from "./Settings";
import type { Options } from "./vf";
import type { Options } from "./vineflower/vineflower";
import type { DecompileResult } from "../workers/decompile/types";
import * as worker from "../workers/decompile/client";
import type { Jar } from "../utils/Jar";
Expand Down Expand Up @@ -71,7 +71,7 @@ export async function getClassBytecode(className: string, jar: Jar) {
export async function decompileClass(className: string, jar: Jar) {
try {
decompilerCounter.next(decompilerCounter.value + 1);
return await worker.decompileClass(className, jar);
return await worker.decompileClass(className, jar, vineflowerVersion.value);
} finally {
decompilerCounter.next(decompilerCounter.value - 1);
}
Expand Down
38 changes: 38 additions & 0 deletions src/logic/Permalink.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,44 @@ describe('Permalink', () => {
});
});

describe('Version 2 URLs (/2/)', () => {
it('should parse a /2/ permalink with version 2', () => {
const state = parsePathToState('2/1.21/net/minecraft/ChatFormatting')!;

expect(state.version).toBe(2);
expect(state.minecraftVersion).toBe('1.21');
expect(state.file).toBe('net/minecraft/ChatFormatting.class');
expect(state.selectedLines).toBe(null);
});

it('should parse a /2/ permalink with line numbers', () => {
const state = parsePathToState('2/1.21.4/net/minecraft/server/MinecraftServer#L100-200')!;

expect(state.version).toBe(2);
expect(state.minecraftVersion).toBe('1.21.4');
expect(state.file).toBe('net/minecraft/server/MinecraftServer.class');
expect(state.selectedLines).toEqual({ line: 100, lineEnd: 200 });
});

it('should parse a /2/ diff permalink', () => {
const state = parsePathToState('2/diff/1.21/1.21.4/net/minecraft/ChatFormatting')!;

expect(state.version).toBe(2);
expect(state.minecraftVersion).toBe('1.21.4');
expect(state.file).toBe('net/minecraft/ChatFormatting.class');
expect(state.diff).toEqual({ leftMinecraftVersion: '1.21' });
});

it('should parse a /2/ diff permalink without a file', () => {
const state = parsePathToState('2/diff/1.21/1.21.4')!;

expect(state.version).toBe(2);
expect(state.minecraftVersion).toBe('1.21.4');
expect(state.file).toBeUndefined();
expect(state.diff).toEqual({ leftMinecraftVersion: '1.21' });
});
});

describe('Real-world Examples', () => {
it('should parse multiline permalink', () => {
const state = parsePathToState('1/1.21.4/net/minecraft/server/MinecraftServer#L250-260')!;
Expand Down
13 changes: 8 additions & 5 deletions src/logic/Permalink.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { combineLatest } from "rxjs";
import { resetPermalinkAffectingSettings, supportsPermalinking } from "./Settings";
import { diffLeftSelectedMinecraftVersion, diffView, selectedFile, selectedLines, selectedMinecraftVersion } from "./State";
import { diffLeftSelectedMinecraftVersion, diffView, vineflowerVersion, selectedFile, selectedLines, selectedMinecraftVersion } from "./State";
import { vineflowerVersionToPermalinkVersion } from "./vineflower/versions";

export interface State {
version: number; // Allows us to change the permalink structure in the future
Expand All @@ -16,7 +17,7 @@ export interface State {
}

const DEFAULT_STATE: State = {
version: 0,
version: 2,
minecraftVersion: "",
file: undefined,
selectedLines: null
Expand Down Expand Up @@ -117,14 +118,16 @@ if (typeof window !== "undefined") {
selectedFile,
selectedLines,
supportsPermalinking,
diffView
diffView,
vineflowerVersion
]).subscribe(([
minecraftVersion,
diffLeftMinecraftVersion,
file,
selectedLines,
supported,
diffView
diffView,
vineflowerVersion
]) => {
if (!file && !diffView) {
document.title = "mcsrc.dev";
Expand All @@ -146,7 +149,7 @@ if (typeof window !== "undefined") {
return;
}

let url = '/1/';
let url = `/${vineflowerVersionToPermalinkVersion(vineflowerVersion)}/`;

if (diffView) {
url += `diff/${diffLeftMinecraftVersion}/${minecraftVersion}`;
Expand Down
4 changes: 4 additions & 0 deletions src/logic/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BehaviorSubject } from "rxjs";
import { pairwise } from "rxjs/operators";
import { Tab, CodeTab } from "./tabs";
import { getInitialState } from "./Permalink";
import { DEFAULT_VERSION, getVersionFromPermalink, type Version } from "./vineflower/versions";

const initialState = getInitialState();

Expand All @@ -17,6 +18,7 @@ export const openTabs = new BehaviorSubject<Tab[]>(initialTab ? [initialTab] : [
export const tabHistory = new BehaviorSubject<string[]>(initialState.file ? [initialState.file] : []);
export const searchQuery = new BehaviorSubject("");
export const referencesQuery = new BehaviorSubject("");
export const vineflowerVersion = new BehaviorSubject<Version>(getVersionFromPermalink(initialState.version));

export interface SelectedLines {
line: number;
Expand All @@ -28,8 +30,10 @@ export const diffView = new BehaviorSubject<boolean>(!!initialState.diff);
export const diffLeftSelectedMinecraftVersion = new BehaviorSubject<string | null>(initialState.diff?.leftMinecraftVersion ?? null);

// Reset selected lines when file changes (skip initial emission to preserve permalink selection)
// Also reset the permalink version back to the latest
selectedFile.pipe(pairwise()).subscribe(([previousFile, currentFile]) => {
if (previousFile !== currentFile) {
selectedLines.next(null);
vineflowerVersion.next(DEFAULT_VERSION);
}
});
22 changes: 22 additions & 0 deletions src/logic/vineflower/versions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const DEFAULT_VERSION = "1.12.0";
export type Version = "1.11.2" | "1.12.0";

export function getVersionFromPermalink(permalinkVersion: number): Version {
switch (permalinkVersion) {
case 1:
return "1.11.2";
case 2:
return "1.12.0";
default:
return DEFAULT_VERSION;
}
}

export function vineflowerVersionToPermalinkVersion(vineflowerVersion: Version): number {
switch (vineflowerVersion) {
case "1.11.2":
return 1;
case "1.12.0":
return 2;
}
}
14 changes: 6 additions & 8 deletions src/logic/vf.ts → src/logic/vineflower/vf-1.11.2.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import wasmPath from "@run-slicer/vf/vf.wasm?url";
import { load } from "@run-slicer/vf/vf.wasm-runtime.js";
import type * as vf from "@run-slicer/vf";

export type * from "@run-slicer/vf";
import wasmPath from "vf-1.11.2/vf.wasm?url";
import { load } from "vf-1.11.2/vf.wasm-runtime.js";
import type * as vf from "vf-1.11.2";

let runtime: typeof vf | null = null;
let runtimePreferWasm = true;

export async function loadRuntime(preferWasm: boolean) {
if (!runtime || runtimePreferWasm !== preferWasm) {
runtimePreferWasm = preferWasm;
console.log(`Loading VineFlower ${preferWasm ? "WASM" : "JavaScript"} runtime`);
console.log(`Loading VineFlower 1.11.2 ${preferWasm ? "WASM" : "JavaScript"} runtime`);

let loadJs = !preferWasm;
if (preferWasm) {
Expand All @@ -25,12 +23,12 @@ export async function loadRuntime(preferWasm: boolean) {
}

if (loadJs) {
runtime = await import("@run-slicer/vf/vf.runtime.js");
runtime = await import("vf-1.11.2/vf.runtime.js");
}
}
}

export const decompile: typeof vf.decompile = async (name, options) => {
export const decompile: typeof vf.decompile = async (name: string | string[], options?: vf.Config) => {
if (!runtime) throw "No runtime loaded";
return await runtime.decompile(name, options);
};
Loading
Loading