Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
725 changes: 345 additions & 380 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"license": "MIT",
"dependencies": {
"@cordisjs/logger": "^0.1.3",
"@kamyu/kbinxml": "^2.0.2",
"@geekidos/kbinxml": "^2.0.4",
"@kamyu/lz77": "^1.0.0",
"@koa/cors": "^5.0.0",
"@koa/router": "^12.0.1",
"arc4": "^2.1.4",
Expand All @@ -59,6 +60,7 @@
"koa-bodyparser": "^4.4.1",
"lodash": "^4.17.21",
"mongodb": "^6.4.0",
"node-cache": "^5.1.2",
"pug": "^3.0.2",
"reflect-metadata": "^0.2.1",
"tslib": "~2.6",
Expand Down
4 changes: 3 additions & 1 deletion src/apis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import Router from "@koa/router";
import { p2dRoutes } from "./p2d/index.js";
import { DefaultContext, DefaultState, ParameterizedContext } from "koa";
import { ILaochanContext } from "../types.js";
import { sdvxRoutes } from './sdvx/index.js';

const router = new Router({
prefix: '/api',
})
.use(p2dRoutes);
.use(p2dRoutes)
.use(sdvxRoutes);

export const apiRoutes = router.routes();
export type Context = ParameterizedContext<DefaultState, ILaochanContext & Router.RouterParamContext<DefaultState, DefaultContext>, any>;
Expand Down
70 changes: 70 additions & 0 deletions src/apis/sdvx/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Router from '@koa/router';
import { Context } from '../index.js';
import {UserService} from '../../services/sdvx/user.js';
import { SaveData } from '../../types/sdvx/savedata.js';
import { PlayerMusicData } from '../../types/sdvx/index.js';
import { tokenToCardNumber } from '../../utils/laochan-id.js';
import { toObject } from '../../utils/kbinxml.js';
import { Serializable } from '../../utils/kxml-value.js';

interface SdvxDataExport {
sdvx_profile: {
"1": {
card: string;
pin: "0000";
sdvx_id: string;
version: {
"6": SaveData;
}
}
}
sdvx_scores_best: Record<string, PlayerMusicData>;
skill_scores_best: {};
}

const router = new Router({
prefix: '/sdvx',
})
.get('/data_export/:token', async (ctx: Context) => {
const { token } = ctx.params;

if (!token) {
ctx.status = 400;
return;
}

const userService = ctx.resolve(UserService);

if (!await userService.hasPlayerData(token)){
ctx.status = 404;
return;
}

const [playerData, musicDatas] = await Promise.all([
userService.getPlayerData(token),
userService.getPlayerMusicDatas(token),
]);

const scoreBests: Record<string, PlayerMusicData> = {};
musicDatas.forEach((m, i) => {
scoreBests[i.toString()] = m;
})

ctx.status = 200;
ctx.body = {
sdvx_profile: {
"1": {
card: tokenToCardNumber(token),
pin: '0000',
sdvx_id: playerData.sdvx_id.__value as string,
version:{
"6": toObject(playerData as unknown as Serializable),
}
}
},
sdvx_scores_best: scoreBests,
skill_scores_best: {}
} as SdvxDataExport;
})

export const sdvxRoutes = router.routes();
4 changes: 2 additions & 2 deletions src/controllers/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ export class DefaultService {
};

// handle ac relay
if (ctx.eacnetRequest) {
if (ctx.acRelayInfo) {
result['xrpc_status_code'] = v.s32(0);
result['xrpc_fault_code'] = v.s32(0);
result['response'] = {
[ctx.eacnetRequest.module]: {
[ctx.acRelayInfo.module]: {
$status: 0,
}
}
Expand Down
133 changes: 54 additions & 79 deletions src/controllers/sdvx/ac-relay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,100 +2,75 @@ import { generic } from "../../decorators/eacnet.js";
import { Context } from "../../types.js";
import { Serializable, v } from "../../utils/kxml-value.js";
import * as data from '../../datas/sdvx.js';
import { requestEa3 } from "../../utils/ea3.js";
import { SDVX_AC_MODEL } from "./index.js";
import { tokenToCardNumber } from '../../utils/laochan-id.js';
import { cache } from '../../decorators/cache.js';

export class AcRelay {
@generic()
async sv6_play_e(): Promise<Serializable> {
async acRelayCommon(ctx: Context): Promise<Serializable> {
const { status, response } = await requestEa3(ctx.acRelayInfo, SDVX_AC_MODEL, ctx.token);

return {
status: v.s32(0),
error_code: v.s32(0),
xrpc_status_code: v.s32(0),
xrpc_fault_code: v.s32(0),
response: {
game: {
$status: 0,
},
}
xrpc_status_code: v.s32(status),
xrpc_fault_code: v.s32(status),
response,
};
}

@generic()
async sv6_play_s(): Promise<Serializable> {
return {
status: v.s32(0),
error_code: v.s32(0),
xrpc_status_code: v.s32(0),
xrpc_fault_code: v.s32(0),
response: {
game: {
$status: 0,
play_id: v.u32(Math.floor(new Date().valueOf() / 6e4)),
},
}
};
async sv6_play_e(ctx: Context): Promise<Serializable> {
return this.acRelayCommon(ctx);
}

@generic()
async sv6_hiscore() {
return {
status: v.s32(0),
error_code: v.s32(0),
xrpc_status_code: v.s32(0),
xrpc_fault_code: v.s32(0),
response: {
game: {
$status: 0,
sc: {},
}
}
};
async sv6_play_s(ctx: Context): Promise<Serializable> {
return this.acRelayCommon(ctx);
}

@generic()
async sv6_log(): Promise<Serializable> {
return {
status: v.s32(0),
error_code: v.s32(0),
xrpc_status_code: v.s32(0),
xrpc_fault_code: v.s32(0),
response: {
game: {
$status: 0,
}
}
};
async sv6_hiscore(ctx: Context) {
return this.acRelayCommon(ctx);
}

@generic()
async sv6_common(): Promise<Serializable> {
return {
status: v.s32(0),
error_code: v.s32(0),
xrpc_status_code: v.s32(0),
xrpc_fault_code: v.s32(0),
response: {
game: {
$status: 0,
music: {},
event: {
info: data.eventList.map(id => ({
event_id: v.str(id),
}))
},
extend: {
info: data.extendInfos,
},
music_limited: {
info: data.musicInfos,
},
skill_course: {
info: data.skillCourses,
},
appealcard: {},
valgene: {},
}
}
}
async sv6_log(ctx: Context): Promise<Serializable> {
return this.acRelayCommon(ctx);
}

@cache('sv6_common')
async sv6_common(ctx: Context): Promise<Serializable> {
return this.acRelayCommon(ctx);
}

async sv6_save_m(ctx: Context): Promise<Serializable> {
return this.acRelayCommon(ctx);
}

async sv6_load_r(ctx: Context): Promise<Serializable> {
return this.acRelayCommon(ctx);
}

async sv6_load_m(ctx: Context): Promise<Serializable> {
return this.acRelayCommon(ctx);
}

async sv6_save_c(ctx: Context): Promise<Serializable> {
return this.acRelayCommon(ctx);
}

async sv6_save(ctx: Context): Promise<Serializable> {
return this.acRelayCommon(ctx);
}

async sv6_new(ctx: Context): Promise<Serializable> {
return this.acRelayCommon(ctx);
}

async sv6_load(ctx: Context): Promise<Serializable> {
// hack for maomani
// chou xiang amao
ctx.acRelayInfo.request['cardid'] = v.str(tokenToCardNumber(ctx.token));
return this.acRelayCommon(ctx);
}

@generic()
Expand Down
95 changes: 92 additions & 3 deletions src/controllers/sdvx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,106 @@ import { Combine } from "../../utils/combine.js";
import { AcRelay } from "./ac-relay.js";
import { UserService } from "../../services/sdvx/user.js";
import { inject, singleton } from "tsyringe";
import { User } from "./user.js";
import { Music } from "./music.js";
import { EacGeneric } from "../eac-generic.js";
import { generic } from "../../decorators/eacnet.js";
import { Context } from "../../types.js";
import { Serializable, v } from "../../utils/kxml-value.js";
import { requestEa3Typed } from '../../utils/ea3.js';
import { tokenToCardNumber, tokenToSnsId } from "../../utils/laochan-id.js";

import * as data from '../../datas/sdvx.js';

export const SDVX_AC_MODEL = 'KFC:A:B:C:20250414';

@singleton()
export default class extends Combine(AcRelay, User, Music, EacGeneric) {
export default class extends Combine(AcRelay, EacGeneric) {
constructor(
@inject(UserService) private readonly userService: UserService,
) {
super();

this.userService;
}

@generic()
async getItemList(): Promise<Serializable> {
return {
status: v.s32(0),
error_code: v.s32(0),
response: {
item_num: v.s32(data.itemList.length),
item: data.itemList.map(id => ({
item_id: v.str(id),
not_free_count: v.s32(1),
free_count: v.s32(0),
})),
}
};
}

@generic()
async getUserIDs(ctx: Context): Promise<Serializable> {
const card = tokenToCardNumber(ctx.token);

const { cardmng: inquire } = await requestEa3Typed<{
cardmng: {
status: string;
dataid: string;
refid: string;
}
}>({
module: 'cardmng',
method: 'inquire',
request: {
$cardtype: '4',
$update: '0'
}
}, SDVX_AC_MODEL, ctx.token);

if (inquire.status == '0') {
return {
status: v.s32(0),
error_code: v.s32(0),
response: {
card_num: v.str(card),
ref_id: v.str(inquire.refid),
data_id: v.str(inquire.dataid),
sns_id: v.str(tokenToSnsId(ctx.token)),
},
};
}

const { cardmng: getrefid } = await requestEa3Typed<{
cardmng: {
status: string;
dataid: string;
refid: string;
}
}>({
module: 'cardmng',
method: 'getrefid',
request: {
// TODO: use hash from token instead of 0000?
$passwd: '0000',
}
}, SDVX_AC_MODEL, ctx.token);

if (getrefid.status !== '0') {
return {
status: v.s32(+getrefid.status),
error_code: v.s32(-1),
}
}

return {
status: v.s32(0),
error_code: v.s32(0),
response: {
card_num: v.str(card),
ref_id: v.str(getrefid.refid),
data_id: v.str(getrefid.dataid),
sns_id: v.str(tokenToSnsId(ctx.token)),
},
};
}
}
Loading