yuanshen/Yuanshen.Patch/dist/redirect.js
wisedev 61cc2055cf First working version of the server
KCP gameserver with packet dispatch, scene streaming, combat, gacha and the
shop, plus a small HTTP server for the login flow and the wish history page.

Game data under assets is pulled out of the client instead of being typed by
hand. The excel tables come from the packed bin container and the spawn points
from the compiled scene scripts.
2026-09-04 02:06:53 +03:00

143 lines
5.1 KiB
JavaScript

function main(stage, parameters) {
const redirectHost = parameters.redirectHost || "http://127.0.0.1:8899";
const setUrl = Resolve.Method("UnityEngine.UnityWebRequestModule.dll", "UnityEngine.Networking", "UnityWebRequest", "set_url", 1);
const uriCtor = Resolve.Method("System.dll", "System", "Uri", ".ctor", 1);
if (setUrl) Interceptor.attach(setUrl, new UrlRewriter(redirectHost));
if (uriCtor) Interceptor.attach(uriCtor, new UrlRewriter(redirectHost));
const browserLoad = Resolve.Method("ZFBrowser.dll", "ZenFulcrum.EmbeddedBrowser", "Browser", "LoadURL", 2);
if (browserLoad) {
Interceptor.attach(browserLoad, {
onEnter(args) {
const url = args[1].readManagedString();
const prefix = url.split('/', 3).join('/');
args[1] = Il2Cpp.NewString(url.replace(prefix, redirectHost));
}
});
}
console.log("[Yuanshen] dispatch redirect active -> " + redirectHost);
if (parameters.levelProbe === true) attachLevelProbe();
if (parameters.debugMenu !== false) enableDebugMenu();
}
const GlobalVars = {
cctor: 0x4BE8CA0,
typeInfo: 0xBE814B8,
staticFields: 0xA0,
debugMenu: 0xF8
};
function enableDebugMenu() {
const base = Module.findBaseAddress("UserAssembly.dll");
if (base === null) {
console.log("[Yuanshen] debug menu: UserAssembly.dll not loaded");
return;
}
Interceptor.attach(base.add(GlobalVars.cctor), {
onLeave() {
const klass = base.add(GlobalVars.typeInfo).readPointer();
if (klass.isNull()) return;
const statics = klass.add(GlobalVars.staticFields).readPointer();
if (statics.isNull()) return;
statics.add(GlobalVars.debugMenu).writeU8(1);
console.log("[Yuanshen] debug menu enabled - Ctrl+` opens it, ` alone opens GM talk");
}
});
}
const LevelProbe = {
"MonsterDataItem.UpdateNormalProp": 0x15D9000,
"LCBaseCombat.UpdateCombatProp": 0x21A2720,
"LCMonsterCombat.ChangeLevel": 0x2586740,
"VCCombatBillboardPlugin.ChangeLevel": 0x21D4AD0,
"VCCombatBillboardPlugin.InitCombatUI": 0x21D2A30
};
function attachLevelProbe() {
const base = Module.findBaseAddress("UserAssembly.dll");
if (base === null) {
console.log("[Yuanshen] level probe: UserAssembly.dll not loaded");
return;
}
for (const name in LevelProbe) {
Interceptor.attach(base.add(LevelProbe[name]), {
onEnter(args) {
console.log("[level] " + name +
" a1=" + args[1].toInt32() +
" a2=" + args[2].toInt32() +
" a3=" + args[3].toInt32());
}
});
}
console.log("[Yuanshen] level probe attached at " + base);
}
function UrlRewriter(redirectHost) {
this.onEnter = function (args) {
const url = args[1].readManagedString();
if (!url.startsWith("http://") && !url.startsWith("https://")) return;
if (url.includes(redirectHost.replace(/^https?:\/\//, ''))) return;
const prefix = url.split('/', 3).join('/');
args[1] = Il2Cpp.NewString(url.replace(prefix, redirectHost));
};
}
const Il2Cpp = {
NewString(value) {
const buffer = Memory.allocUtf16String(value);
return this.Api('il2cpp_string_new_utf16', 'pointer', ['pointer', 'int'], [buffer, value.length]);
},
Api(name, ret, argTypes, args) {
const fn = new NativeFunction(Module.findExportByName(null, name), ret, argTypes);
return fn.apply(fn, args);
}
};
const Resolve = {
Method(moduleName, namespace, className, methodName, argCount) {
const image = this.Image(moduleName);
if (image.isNull()) return null;
const klass = Il2Cpp.Api('il2cpp_class_from_name', 'pointer', ['pointer', 'pointer', 'pointer'],
[image, Memory.allocUtf8String(namespace), Memory.allocUtf8String(className)]);
if (klass.isNull()) return null;
const method = Il2Cpp.Api('il2cpp_class_get_method_from_name', 'pointer', ['pointer', 'pointer', 'int'],
[klass, Memory.allocUtf8String(methodName), argCount]);
if (method.isNull()) return null;
return method.readPointer();
},
Image(name) {
const domain = Il2Cpp.Api('il2cpp_domain_get', 'pointer', [], []);
const sizeOut = Memory.alloc(8);
const assemblies = Il2Cpp.Api('il2cpp_domain_get_assemblies', 'pointer', ['pointer', 'pointer'], [domain, sizeOut]);
const size = sizeOut.readU64();
for (let i = 0; i < size; i++) {
const assembly = assemblies.add(i * 8).readPointer();
const image = Il2Cpp.Api('il2cpp_assembly_get_image', 'pointer', ['pointer'], [assembly]);
const imageName = Il2Cpp.Api('il2cpp_image_get_name', 'pointer', ['pointer'], [image]);
if (imageName.readUtf8String() === name) return image;
}
return NULL;
}
};
NativePointer.prototype.readManagedString = function () {
const length = this.add(16).readInt();
return this.add(20).readUtf16String(length);
};
rpc.exports.init = main;