From 3515953249281cd3cc9728e4d1cc490a348d37b8 Mon Sep 17 00:00:00 2001 From: alexay7 <43906716+alexay7@users.noreply.github.com> Date: Mon, 13 Oct 2025 20:49:06 +0200 Subject: [PATCH] Support for X-Verse --- apps/server/chusan/src/index.ts | 14 ++--- .../chusan/src/service/core/encryption.ts | 4 ++ .../chusan/src/service/core/matching.ts | 60 ++++++++++++------- 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/apps/server/chusan/src/index.ts b/apps/server/chusan/src/index.ts index 2de7bab..1cee1cb 100644 --- a/apps/server/chusan/src/index.ts +++ b/apps/server/chusan/src/index.ts @@ -10,7 +10,7 @@ import { pingMatching, endMatching, removeMatchingMember, - getMatchingState, + getMatchingState, mBeginRaidMatchingApi, mGetRaidMatchingStateApi, mEndRaidMatchingApi, mRemoveRaidMatchingMemberApi, } from './service' import { mPing, mBeginMatchingApi, mEndMatchingApi, mGetMatchingStateApi, mRemoveMatchingMemberApi, calculateEncryption, gameKeys } from './service/core/encryption' import { buffer } from 'stream/consumers' @@ -22,7 +22,7 @@ app.use(function(req, res, next) { req.body = ''; req.setEncoding('hex'); - req.on('data', function(chunk) { + req.on('data', function(chunk) { req.body += chunk; }); @@ -41,7 +41,7 @@ app.use((req, res, next) => { const header = req.headers if (header['chuni-encoding'] != undefined){ req.body = decrypt( - gameKeys[header['chuni-encoding'].valueOf().toString()], + gameKeys[header['chuni-encoding'].valueOf().toString()], Buffer.from(req.body, 'hex')) req.body = zlib.inflateSync(req.body).toString() req.body = JSON.parse(req.body) @@ -111,28 +111,28 @@ app.post('/MatchingServer/:api', async (req, res) => { }) return res.json(await pingMatching()) } else - if (mBeginMatchingApi.includes(api)){ + if (mBeginMatchingApi.includes(api) || mBeginRaidMatchingApi.includes(api)){ log.info({ service: 'Yukiotoko Matching', message: `BeginMatching`, }) return res.json(await beginMatching(body, req.ip)) } else - if (mEndMatchingApi.includes(api)){ + if (mEndMatchingApi.includes(api) || mEndRaidMatchingApi.includes(api)){ log.info({ service: 'Yukiotoko Matching', message: `EndMatching`, }) return res.json(await endMatching(body)) } else - if (mRemoveMatchingMemberApi.includes(api)){ + if (mRemoveMatchingMemberApi.includes(api) || mRemoveRaidMatchingMemberApi.includes(api)){ log.info({ service: 'Yukiotoko Matching', message: `RemoveMatchingMember`, }) return res.json(await removeMatchingMember(body)) } else - if (mGetMatchingStateApi.includes(api)){ + if (mGetMatchingStateApi.includes(api) || mGetRaidMatchingStateApi.includes(api)){ log.info({ service: 'Yukiotoko Matching', message: `GetMatchingState`, diff --git a/apps/server/chusan/src/service/core/encryption.ts b/apps/server/chusan/src/service/core/encryption.ts index e22b2d9..ceebd51 100644 --- a/apps/server/chusan/src/service/core/encryption.ts +++ b/apps/server/chusan/src/service/core/encryption.ts @@ -7,6 +7,10 @@ export var mBeginMatchingApi = ['BeginMatchingApi'] export var mEndMatchingApi = ['EndMatchingApi'] export var mRemoveMatchingMemberApi = ['RemoveMatchingMemberApi'] export var mGetMatchingStateApi = ['GetMatchingStateApi'] +export var mBeginRaidMatchingApi = ['BeginRaidMatchingApi'] +export var mGetRaidMatchingStateApi = ['GetRaidMatchingStateApi'] +export var mEndRaidMatchingApi = ['EndRaidMatchingApi'] +export var mRemoveRaidMatchingMemberApi = ['RemoveRaidMatchingMemberApi'] //Insert Keys Here export const gameKeys = { diff --git a/apps/server/chusan/src/service/core/matching.ts b/apps/server/chusan/src/service/core/matching.ts index 93b41b5..d4d5417 100644 --- a/apps/server/chusan/src/service/core/matching.ts +++ b/apps/server/chusan/src/service/core/matching.ts @@ -1,6 +1,12 @@ import { db, Prisma } from '@yukiotoko/db' -const convertIso88591ToUtf8 = (input: string): string => { +const convertIso88591ToUtf8 = (input: string, romVersion:string): string => { + // From X-Verse the usernames are sent in UTF-8 instead of ISO-8859-1 + const isUtf8 = convertRomVersionToNumber(romVersion) >= 24000 + if (isUtf8) { + return input + } + const latin1Bytes = new Uint8Array(input.length) for (let i = 0; i < input.length; i++) { latin1Bytes[i] = input.charCodeAt(i) @@ -10,6 +16,20 @@ const convertIso88591ToUtf8 = (input: string): string => { return decoder.decode(latin1Bytes) } +const convertRomVersionToNumber = (romVersion: string): number => { + //Convert rom version string to a number for easier comparison + //Example: "2.40.00" -> 24000 + const versionParts = romVersion.split('.') + if (versionParts.length !== 3) { + return 0 // Invalid version format + } + const major = parseInt(versionParts[0]) || 0 + const minor = parseInt(versionParts[1]) || 0 + const patch = parseInt(versionParts[2]) || 0 + + return major * 10000 + minor * 100 + patch +} + export const pingMatching = () => { return { returnCode: 1 } } @@ -31,7 +51,7 @@ export const beginMatching = async (body, ip) => { } //UserID + Name ban case 2: { - if (body.matchingMemberInfo.userId == banEntry.value && convertIso88591ToUtf8(body.matchingMemberInfo.userName) == banEntry.value2) { + if (body.matchingMemberInfo.userId == banEntry.value && convertIso88591ToUtf8(body.matchingMemberInfo.userName,body.matchingMemberInfo.romVersion) == banEntry.value2) { banResult = { message: "User Is Banned." } @@ -60,9 +80,9 @@ export const beginMatching = async (body, ip) => { var nowdate = new Date() olddate.setTime(olddate.getTime() + (10*1000)) DeleteAfterDay.setTime(DeleteAfterDay.getTime() + (24*60*60*1000)) - //If there is a room that has not been updated in 10 seconds and is not finished + //If there is a room that has not been updated in 10 seconds and is not finished //(Happens when host test menus while solo), mark the room as finished. - if ((nowdate > olddate) && (room.isFinished == false)){ + if ((nowdate > olddate) && (room.isFinished == false)){ await db.chusanUserMatching.updateMany({ where: { roomId: room.roomId @@ -71,9 +91,9 @@ export const beginMatching = async (body, ip) => { isFinished: true } }) - } + } // If the room has not been updated in 24 hours, delete the room entirely - if (nowdate > DeleteAfterDay){ + if (nowdate > DeleteAfterDay){ await db.chusanUserMatching.deleteMany({ where: { roomId: room.roomId @@ -88,7 +108,7 @@ export const beginMatching = async (body, ip) => { userId: body.matchingMemberInfo.userId, isFinished: false, } - }) + }) var roomid = 1 // Find the oldest room that is within 2 battle ranks of the player, and the same rom and data version //Make sure the battle rank id is at the very least 0 when checking @@ -120,7 +140,7 @@ export const beginMatching = async (body, ip) => { } } ] - + }, orderBy: { roomId: 'asc', @@ -137,8 +157,8 @@ export const beginMatching = async (body, ip) => { roomid = newest_chusanUserMatching.roomId + 1 } var host = body.matchingMemberInfo - host.userName = convertIso88591ToUtf8(body.matchingMemberInfo.userName) - host.teamName = convertIso88591ToUtf8(body.matchingMemberInfo.teamName) + host.userName = convertIso88591ToUtf8(body.matchingMemberInfo.userName,body.matchingMemberInfo.romVersion) + host.teamName = convertIso88591ToUtf8(body.matchingMemberInfo.teamName,body.matchingMemberInfo.romVersion) // Create the room chusanUserMatching = await db.chusanUserMatching.create({ data: { @@ -160,8 +180,8 @@ export const beginMatching = async (body, ip) => { roomid = chusanUserMatching.roomId const matching_member_list = chusanUserMatching.matchingMemberInfoList as Prisma.JsonArray var new_member = body.matchingMemberInfo - new_member.userName = convertIso88591ToUtf8(body.matchingMemberInfo.userName,) - new_member.teamName = convertIso88591ToUtf8(body.matchingMemberInfo.teamName,) + new_member.userName = convertIso88591ToUtf8(body.matchingMemberInfo.userName, body.matchingMemberInfo.romVersion) + new_member.teamName = convertIso88591ToUtf8(body.matchingMemberInfo.teamName, body.matchingMemberInfo.romVersion) matching_member_list.push(new_member) var isfull =false if (matching_member_list.length == 4){ @@ -182,7 +202,7 @@ export const beginMatching = async (body, ip) => { } // Delete any cabs still looking to UDP that is going to start matching again // Or delete any rooms that shouldnt exist - await db.chusanUserRooms.deleteMany({ + await db.chusanUserRooms.deleteMany({ where: { OR: [ { @@ -193,7 +213,7 @@ export const beginMatching = async (body, ip) => { }, ] } - }) + }) const matchingWaitState = { isFinish: false, restMSec: 60, @@ -231,7 +251,7 @@ export const endMatching = async body => { console.log(averageBattleRanking) averageBattleRanking = averageBattleRanking / members.length // Return roles for every player - // 0 = CPU (Need to confirm?) 1 = Host 2 = Client + // 0 = CPU (Need to confirm?) 1 = Host 2 = Client members.forEach(member => { if (member!['userId'] == matching_room?.userId){ role_list.push({ role: 1 }) @@ -326,11 +346,11 @@ export const getMatchingState = async body => { var roomMembers = matching_room!.matchingMemberInfoList as Prisma.JsonArray var current_member = body.matchingMemberInfo var allowAnybody = false - current_member.userName = convertIso88591ToUtf8(body.matchingMemberInfo.userName) - current_member.teamName = convertIso88591ToUtf8(body.matchingMemberInfo.teamName) + current_member.userName = convertIso88591ToUtf8(body.matchingMemberInfo.userName,body.matchingMemberInfo.romVersion) + current_member.teamName = convertIso88591ToUtf8(body.matchingMemberInfo.teamName,body.matchingMemberInfo.romVersion) //If the api packet is from the host, update the time if (matching_room!.userId == String(current_member.userId)) { - + if(matching_room!.matchingMemberInfoList.length == 4) { restsec = 0 } @@ -372,7 +392,7 @@ export const getMatchingState = async body => { }, }) var olddate = new Date(matching_room.updatedAt) - var nowdate = new Date() + var nowdate = new Date() // if the host has stopped updating the room for 10 seconds(Because of test menu), delete the room because all other clients are softlocked olddate.setTime(olddate.getTime() + (10*1000)) if ((nowdate > olddate) && (matching_room.isFinished == false)){ @@ -381,7 +401,7 @@ export const getMatchingState = async body => { id: matching_room.id } }) - } + } // If the current room is allowing anybody and only has 1 player in it, try to merge rooms if((matching_room.restMSec < 45) && (matching_room.matchingMemberInfoList.length == 1)){ var newroom = await db.chusanUserMatching.findFirst({