mirror of
https://github.com/asphyxia-core/plugins.git
synced 2026-08-09 03:11:34 -05:00
feat/op3-support
This commit is contained in:
parent
95e6eade6d
commit
160f633454
|
|
@ -1,12 +1,13 @@
|
|||
# Nostalgia
|
||||
|
||||
Plugin Version: **v1.2.0**
|
||||
Plugin Version: **v1.3.0**
|
||||
|
||||
Supported Versions
|
||||
-------------------
|
||||
- ノスタルジア/ First Version (Experiment-Old)
|
||||
- Forte (Experiment-Old)
|
||||
- Op.2
|
||||
- Op.3 + omnimix(up to 08/07/2024)
|
||||
|
||||
About Experiment-Old Support
|
||||
----------------------------
|
||||
|
|
@ -17,7 +18,11 @@ If you have a problem that move from old version to new version, There's webui f
|
|||
|
||||
Changelog
|
||||
=========
|
||||
1.2.0 (Current)
|
||||
1.3.0
|
||||
---------------
|
||||
- Op.3 + omnimix(up to 08/07/2024) support
|
||||
|
||||
1.2.0
|
||||
---------------
|
||||
- Nostalgia First version support.
|
||||
|
||||
|
|
|
|||
226
nostalgia@asphyxia/data/Op3Music.ts
Normal file
226
nostalgia@asphyxia/data/Op3Music.ts
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
import { readB64JSON, readXML } from './helper';
|
||||
|
||||
const OP3_XML_CANDIDATES = [
|
||||
'data/op3_mdb.xml',
|
||||
'data/music_list.xml',
|
||||
];
|
||||
|
||||
const OP3_B64 = 'data/op3_mdb.json.b64';
|
||||
const OP3_PROCESSED_B64 = 'data/op3_mdb_processed.json.b64';
|
||||
|
||||
export type ProcessedOp3Music = {
|
||||
revision: string;
|
||||
release_code: string;
|
||||
music_spec: any[];
|
||||
overwrite_spec: any[];
|
||||
max_index: number;
|
||||
};
|
||||
|
||||
type RawSong = Record<string, string | undefined>;
|
||||
|
||||
let processedMemo: ProcessedOp3Music | null = null;
|
||||
|
||||
function songNodes(data: any): any[] {
|
||||
const list = data?.music_list;
|
||||
if (!list) return [];
|
||||
|
||||
const specs = list.music_spec;
|
||||
if (specs) return _.isArray(specs) ? specs : [specs];
|
||||
|
||||
const entries = _.isArray(list) ? list : [list];
|
||||
if (entries.length === 1 && entries[0]?.music_spec) {
|
||||
const inner = entries[0].music_spec;
|
||||
return _.isArray(inner) ? inner : [inner];
|
||||
}
|
||||
|
||||
const root = data.music_list?.['@attr'] ? data.music_list : data;
|
||||
const children = Object.keys(root)
|
||||
.filter((k) => k !== '@attr' && k !== 'music_spec')
|
||||
.flatMap((k) => {
|
||||
const v = root[k];
|
||||
return _.isArray(v) ? v : v ? [v] : [];
|
||||
});
|
||||
|
||||
if (children.length > 0) return children;
|
||||
return [];
|
||||
}
|
||||
|
||||
function readField(node: any, name: string, fallback = '0'): string {
|
||||
const el = node?.[name];
|
||||
if (el == null) return fallback;
|
||||
if (typeof el === 'string' || typeof el === 'number') return `${el}`;
|
||||
return `${_.get(el, '@content.0', fallback)}`;
|
||||
}
|
||||
|
||||
function toSong(node: any): { index: string; fields: RawSong } {
|
||||
const index = `${node?.['@attr']?.index ?? node?.index ?? '0'}`;
|
||||
const fields: RawSong = {};
|
||||
const keys = [
|
||||
'priority', 'category_flag', 'primary_category',
|
||||
'level_normal', 'level_hard', 'level_extreme', 'level_real',
|
||||
'demo_popular', 'demo_bemani',
|
||||
'destination_j', 'destination_a', 'destination_y', 'destination_k',
|
||||
'offline', 'unlock_type', 'volume_bgm', 'volume_key',
|
||||
'jk_jpn', 'jk_asia', 'jk_kor', 'jk_idn',
|
||||
'real_unlock_type', 'real_once_price', 'real_forever_price',
|
||||
];
|
||||
for (const key of keys) {
|
||||
fields[key] = readField(node, key);
|
||||
}
|
||||
return { index, fields };
|
||||
}
|
||||
|
||||
function musicSpec(index: string, s: RawSong, overwrite = false) {
|
||||
const bool = (k: string, def = '1') => K.ITEM('bool', readField({ [k]: s[k] }, k, def) !== '0');
|
||||
const base: any = {
|
||||
basename: K.ITEM('str', ''),
|
||||
title: K.ITEM('str', ''),
|
||||
title_kana: K.ITEM('str', ''),
|
||||
artist: K.ITEM('str', ''),
|
||||
artist_kana: K.ITEM('str', ''),
|
||||
license: K.ITEM('str', ''),
|
||||
license_site: K.ITEM('str', ''),
|
||||
priority: K.ITEM('s8', parseInt(s.priority || '0', 10)),
|
||||
category_flag: K.ITEM('s32', parseInt(s.category_flag || '0', 10)),
|
||||
primary_category: K.ITEM('s8', parseInt(s.primary_category || '0', 10)),
|
||||
level_normal: K.ITEM('s8', parseInt(s.level_normal || '0', 10)),
|
||||
level_hard: K.ITEM('s8', parseInt(s.level_hard || '0', 10)),
|
||||
level_extreme: K.ITEM('s8', parseInt(s.level_extreme || '0', 10)),
|
||||
level_real: K.ITEM('s8', parseInt(s.level_real || '0', 10)),
|
||||
demo_popular: bool('demo_popular', '0'),
|
||||
demo_bemani: bool('demo_bemani', '0'),
|
||||
destination_j: bool('destination_j'),
|
||||
destination_a: bool('destination_a'),
|
||||
destination_y: bool('destination_y'),
|
||||
destination_k: bool('destination_k'),
|
||||
offline: bool('offline', '0'),
|
||||
unlock_type: K.ITEM('s8', parseInt(s.unlock_type || '0', 10)),
|
||||
volume_bgm: K.ITEM('s8', parseInt(s.volume_bgm || '0', 10)),
|
||||
volume_key: K.ITEM('s8', parseInt(s.volume_key || '0', 10)),
|
||||
start_date: K.ITEM('str', '2017-03-01 10:00'),
|
||||
end_date: K.ITEM('str', '9999-12-31 23:59'),
|
||||
expiration_date: K.ITEM('str', '9999-12-31 23:59'),
|
||||
description: K.ITEM('str', ''),
|
||||
};
|
||||
|
||||
if (overwrite) {
|
||||
return K.ATTR({ index }, {
|
||||
jk_jpn: bool('jk_jpn'),
|
||||
jk_asia: bool('jk_asia'),
|
||||
jk_kor: bool('jk_kor'),
|
||||
jk_idn: bool('jk_idn'),
|
||||
unlock_type: K.ITEM('s8', parseInt(s.unlock_type || '0', 10)),
|
||||
real_unlock_type: K.ITEM('s8', parseInt(s.real_unlock_type || '0', 10)),
|
||||
start_date: K.ITEM('str', '2017-03-01 10:00'),
|
||||
end_date: K.ITEM('str', '9999-12-31 23:59'),
|
||||
real_once_price: K.ITEM('s32', parseInt(s.real_once_price || '300', 10)),
|
||||
real_forever_price: K.ITEM('s32', parseInt(s.real_forever_price || '7500', 10)),
|
||||
real_start_date: K.ITEM('str', '2017-03-01 10:00'),
|
||||
real_end_date: K.ITEM('str', '9999-12-31 23:59'),
|
||||
});
|
||||
}
|
||||
|
||||
return K.ATTR({ index }, base);
|
||||
}
|
||||
|
||||
function emptyProcessed(): ProcessedOp3Music {
|
||||
return {
|
||||
revision: '21261',
|
||||
release_code: '2021090800',
|
||||
music_spec: [],
|
||||
overwrite_spec: [],
|
||||
max_index: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function buildProcessedFromRaw(raw: any): ProcessedOp3Music {
|
||||
const attr = raw?.music_list?.['@attr'] ?? {};
|
||||
const revision = `${attr.revision ?? '21261'}`;
|
||||
const release_code = `${attr.release_code ?? '2021090800'}`;
|
||||
const songs = songNodes(raw).map(toSong).filter((s) => parseInt(s.index, 10) > 0);
|
||||
|
||||
let maxIndex = 0;
|
||||
for (const s of songs) {
|
||||
maxIndex = Math.max(maxIndex, parseInt(s.index, 10));
|
||||
}
|
||||
|
||||
return {
|
||||
revision,
|
||||
release_code,
|
||||
music_spec: songs.map((s) => musicSpec(s.index, s.fields, false)),
|
||||
overwrite_spec: songs.map((s) => musicSpec(s.index, s.fields, true)),
|
||||
max_index: maxIndex,
|
||||
};
|
||||
}
|
||||
|
||||
async function readProcessedCache(): Promise<ProcessedOp3Music | null> {
|
||||
if (!IO.Exists(OP3_PROCESSED_B64)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return await readB64JSON(OP3_PROCESSED_B64);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function writeProcessedCache(data: ProcessedOp3Music): Promise<void> {
|
||||
await IO.WriteFile(
|
||||
OP3_PROCESSED_B64,
|
||||
Buffer.from(JSON.stringify(data)).toString('base64')
|
||||
);
|
||||
}
|
||||
|
||||
async function loadRaw(): Promise<any | null> {
|
||||
if (IO.Exists(OP3_B64)) {
|
||||
return readB64JSON(OP3_B64);
|
||||
}
|
||||
|
||||
let xmlPath: string | null = null;
|
||||
for (const candidate of OP3_XML_CANDIDATES) {
|
||||
if (IO.Exists(candidate)) {
|
||||
xmlPath = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!xmlPath) {
|
||||
console.warn('[nostalgia@asphyxia] OP3 music DB missing. Copy music_list.xml to data/op3_mdb.xml');
|
||||
return null;
|
||||
}
|
||||
|
||||
const raw = await readXML(xmlPath);
|
||||
await IO.WriteFile(
|
||||
OP3_B64,
|
||||
Buffer.from(JSON.stringify(raw)).toString('base64')
|
||||
);
|
||||
return raw;
|
||||
}
|
||||
|
||||
export async function processOp3MusicData(): Promise<ProcessedOp3Music & { fromCache: boolean }> {
|
||||
if (processedMemo) {
|
||||
return { ...processedMemo, fromCache: true };
|
||||
}
|
||||
|
||||
const raw = await loadRaw();
|
||||
if (!raw) {
|
||||
const empty = emptyProcessed();
|
||||
processedMemo = empty;
|
||||
return { ...empty, fromCache: false };
|
||||
}
|
||||
|
||||
const attr = raw?.music_list?.['@attr'] ?? {};
|
||||
const revision = `${attr.revision ?? '21261'}`;
|
||||
const release_code = `${attr.release_code ?? '2021090800'}`;
|
||||
|
||||
const cached = await readProcessedCache();
|
||||
if (cached && cached.revision === revision && cached.release_code === release_code) {
|
||||
processedMemo = cached;
|
||||
return { ...cached, fromCache: true };
|
||||
}
|
||||
|
||||
const built = buildProcessedFromRaw(raw);
|
||||
processedMemo = built;
|
||||
await writeProcessedCache(built);
|
||||
return { ...built, fromCache: false };
|
||||
}
|
||||
1
nostalgia@asphyxia/data/op3_mdb.json.b64
Normal file
1
nostalgia@asphyxia/data/op3_mdb.json.b64
Normal file
File diff suppressed because one or more lines are too long
1
nostalgia@asphyxia/data/op3_mdb_processed.json.b64
Normal file
1
nostalgia@asphyxia/data/op3_mdb_processed.json.b64
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
import { processData as firstData } from "../data/FirstMusic";
|
||||
import { processData as forteData } from "../data/ForteMusic";
|
||||
import { processOp3MusicData } from "../data/Op3Music";
|
||||
import { readB64JSON } from "../data/helper";
|
||||
import { NosVersionHelper } from "../utils";
|
||||
|
||||
|
|
@ -81,9 +82,74 @@ export const get_common_info = async (info, data, send) => {
|
|||
});
|
||||
};
|
||||
|
||||
let op3MusicInfoByteCache: { data: any; xCompress: any; xCorePlugin: any } | null = null;
|
||||
|
||||
export const get_music_info: EPR = async (info, data, send) => {
|
||||
const version = new NosVersionHelper(info)
|
||||
|
||||
if (version.isOp3()) {
|
||||
const t0 = Date.now();
|
||||
const op3 = await processOp3MusicData();
|
||||
console.log(
|
||||
`[nostalgia@asphyxia] op3_common.get_music_info ${Date.now() - t0}ms (songs=${op3.max_index}, cache=${op3.fromCache ? 'hit' : 'miss'})`
|
||||
);
|
||||
|
||||
try {
|
||||
const inst: any = send as any;
|
||||
const res: any = inst?.res;
|
||||
if (!inst?.body?.encrypted && res && typeof res.send === 'function' && typeof res.setHeader === 'function') {
|
||||
if (op3MusicInfoByteCache) {
|
||||
res.setHeader('X-Compress', op3MusicInfoByteCache.xCompress ?? 'none');
|
||||
if (op3MusicInfoByteCache.xCorePlugin != null) {
|
||||
res.setHeader('X-CORE-Plugin', op3MusicInfoByteCache.xCorePlugin);
|
||||
}
|
||||
res.send(op3MusicInfoByteCache.data);
|
||||
inst.sent = true;
|
||||
console.log(`[nostalgia@asphyxia] op3_common.get_music_info byte-cache HIT (songs=${op3.max_index})`);
|
||||
return;
|
||||
}
|
||||
const realSend = res.send.bind(res);
|
||||
res.send = (d: any) => {
|
||||
res.send = realSend;
|
||||
try {
|
||||
if (Buffer.isBuffer(d) || typeof d === 'string') {
|
||||
op3MusicInfoByteCache = {
|
||||
data: d,
|
||||
xCompress: res.getHeader('X-Compress'),
|
||||
xCorePlugin: res.getHeader('X-CORE-Plugin'),
|
||||
};
|
||||
}
|
||||
} catch (_e) { /* ignore capture failure */ }
|
||||
return realSend(d);
|
||||
};
|
||||
}
|
||||
} catch (_e) { /* fall back to normal send.object */ }
|
||||
|
||||
send.object({
|
||||
permitted_list,
|
||||
music_list: K.ATTR({
|
||||
revision: op3.revision,
|
||||
release_code: op3.release_code,
|
||||
}, {
|
||||
music_spec: op3.music_spec,
|
||||
}),
|
||||
overwrite_music_list: K.ATTR({
|
||||
revision: op3.revision,
|
||||
release_code: op3.release_code,
|
||||
}, {
|
||||
music_spec: op3.overwrite_spec,
|
||||
}),
|
||||
gamedata_flag_list: {},
|
||||
trend_music_list: {
|
||||
trend_music: K.ATTR({ music_index: '1', rank: '1' }, {}),
|
||||
},
|
||||
olupdate: {
|
||||
delete_flag: K.ITEM('bool', 0),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const music_spec: any = [];
|
||||
for (let i = 1; i < 400; ++i) {
|
||||
music_spec.push(K.ATTR({ index: `${i}` }, {
|
||||
|
|
|
|||
|
|
@ -153,6 +153,205 @@ const getPlayerData = async (refid: string, info: EamuseInfo, name?: string) =>
|
|||
music_list2.pop();
|
||||
}
|
||||
|
||||
return buildLegacyPlayerPayload(p, version, {
|
||||
param,
|
||||
brooch,
|
||||
stairs,
|
||||
kentei_record,
|
||||
island_progress,
|
||||
correct_permitted_list,
|
||||
music_list,
|
||||
music_list2,
|
||||
});
|
||||
};
|
||||
|
||||
const buildMusicFlags = (p: Profile) => ({
|
||||
flag: [
|
||||
K.ARRAY('s32', p.musicList.type_0, { sheet_type: '0' }),
|
||||
K.ARRAY('s32', p.musicList.type_1, { sheet_type: '1' }),
|
||||
K.ARRAY('s32', p.musicList.type_2, { sheet_type: '2' }),
|
||||
K.ARRAY('s32', p.musicList.type_3, { sheet_type: '3' }),
|
||||
],
|
||||
});
|
||||
|
||||
const buildFreeMusicFlags = (p: Profile) => ({
|
||||
flag: [
|
||||
K.ARRAY('s32', p.musicList2.type_0, { sheet_type: '0' }),
|
||||
K.ARRAY('s32', p.musicList2.type_1, { sheet_type: '1' }),
|
||||
K.ARRAY('s32', p.musicList2.type_2, { sheet_type: '2' }),
|
||||
K.ARRAY('s32', p.musicList2.type_3, { sheet_type: '3' }),
|
||||
],
|
||||
});
|
||||
|
||||
const op3Num = (value: number | undefined, fallback = 0) =>
|
||||
typeof value === 'number' && !Number.isNaN(value) ? value : fallback;
|
||||
|
||||
const padS32 = (arr: number[] | undefined, len: number, fill: number) => {
|
||||
const out = Array(len).fill(fill);
|
||||
if (!arr) return out;
|
||||
for (let i = 0; i < Math.min(len, arr.length); i++) {
|
||||
const v = arr[i];
|
||||
out[i] = typeof v === 'number' && !Number.isNaN(v) ? v : fill;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const padOp3MusicLists = (profile: Profile): Profile => ({
|
||||
...profile,
|
||||
musicList: {
|
||||
type_0: padS32(profile.musicList?.type_0, 32, -1),
|
||||
type_1: padS32(profile.musicList?.type_1, 32, -1),
|
||||
type_2: padS32(profile.musicList?.type_2, 32, -1),
|
||||
type_3: padS32(profile.musicList?.type_3, 32, -1),
|
||||
},
|
||||
musicList2: {
|
||||
type_0: padS32(profile.musicList2?.type_0, 32, -1),
|
||||
type_1: padS32(profile.musicList2?.type_1, 32, -1),
|
||||
type_2: padS32(profile.musicList2?.type_2, 32, -1),
|
||||
type_3: padS32(profile.musicList2?.type_3, 32, -1),
|
||||
},
|
||||
params: {
|
||||
'1': padS32(profile.params?.['1'], 11, 0),
|
||||
'2': padS32(profile.params?.['2'], 8, 0),
|
||||
},
|
||||
});
|
||||
|
||||
const op3FreshMusicFlags = () => ({
|
||||
flag: [
|
||||
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '0' }),
|
||||
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '1' }),
|
||||
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '2' }),
|
||||
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '3' }),
|
||||
],
|
||||
});
|
||||
|
||||
const buildOp3Last = (p: Profile, isRegist = false) => {
|
||||
const last: Record<string, unknown> = {
|
||||
music_group: K.ITEM('s32', op3Num(p.group)),
|
||||
music_index: K.ITEM('s32', op3Num(p.music)),
|
||||
sheet_type: K.ITEM('s8', op3Num(p.sheet)),
|
||||
perform_type: K.ITEM('s32', op3Num(p.performType)),
|
||||
filter_flag: K.ITEM('u64', BigInt(op3Num(p.filterFlag))),
|
||||
brooch_index: K.ITEM('s32', op3Num(p.brooch)),
|
||||
hi_speed_level: K.ITEM('s32', op3Num(p.hispeed)),
|
||||
beat_guide: K.ITEM('s8', op3Num(p.beatGuide)),
|
||||
headphone_volume: K.ITEM('s8', op3Num(p.headphone)),
|
||||
judge_bar_pos: K.ITEM('s32', isRegist ? 0 : op3Num(p.judgeBar, 250)),
|
||||
hands_mode: K.ITEM('s8', op3Num(p.mode)),
|
||||
near_setting: K.ITEM('s8', op3Num(p.near)),
|
||||
judge_delay_offset: K.ITEM('s8', op3Num(p.offset)),
|
||||
key_beam_level: K.ITEM('s8', op3Num(p.keyBeam)),
|
||||
orbit_type: K.ITEM('s8', op3Num(p.orbit)),
|
||||
note_height: K.ITEM('s8', op3Num(p.noteHeight, 10)),
|
||||
note_width: K.ITEM('s8', op3Num(p.noteWidth, 10)),
|
||||
judge_width_type: K.ITEM('s8', op3Num(p.judgeWidth, 10)),
|
||||
beat_guide_volume: K.ITEM('s8', op3Num(p.beatVolume)),
|
||||
beat_guide_type: K.ITEM('s8', op3Num(p.beatType)),
|
||||
key_volume_offset: K.ITEM('s8', op3Num(p.keyVolume)),
|
||||
bgm_volume_offset: K.ITEM('s8', op3Num(p.bgmVolume)),
|
||||
note_disp_type: K.ITEM('s8', op3Num(p.note)),
|
||||
slow_fast: K.ITEM('s8', op3Num(p.sf)),
|
||||
option_setting: K.ITEM('s32', op3Num(p.optionSetting)),
|
||||
judge_effect_adjust: K.ITEM('s8', op3Num(p.judgeFX)),
|
||||
simple_bg: K.ITEM('s8', op3Num(p.simple)),
|
||||
bingo_index: K.ITEM('s32', op3Num(p.bingo)),
|
||||
};
|
||||
|
||||
if (!isRegist) {
|
||||
last.class_basic = K.ITEM('s32', op3Num(p.classBasic));
|
||||
last.class_recital = K.ITEM('s32', op3Num(p.classRecital));
|
||||
last.grade_basic = K.ITEM('s32', op3Num(p.gradeBasic));
|
||||
last.grade_recital = K.ITEM('s32', op3Num(p.gradeRecital));
|
||||
}
|
||||
|
||||
return last;
|
||||
};
|
||||
|
||||
const buildOp3Travel = (p: Profile) => ({
|
||||
money: K.ITEM('s32', op3Num(p.money)),
|
||||
pianist_power: K.ITEM('s32', op3Num(p.pianistPower)),
|
||||
fame_index: K.ITEM('s32', op3Num(p.fameId)),
|
||||
kingdom_id: K.ITEM('s32', op3Num(p.kingdomId)),
|
||||
quest_index: K.ITEM('s32', op3Num(p.questIndex)),
|
||||
});
|
||||
|
||||
const buildOp3ExtraParam = () => ({
|
||||
param: [
|
||||
K.ATTR({ type: '1' }, {
|
||||
count: K.ITEM('s32', 11),
|
||||
params_array: K.ARRAY('s32', [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
}),
|
||||
K.ATTR({ type: '2' }, {
|
||||
count: K.ITEM('s32', 8),
|
||||
params_array: K.ARRAY('s32', [64, 0, 0, 0, 0, 0, 0, 0]),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const op3RegistExtras = () => ({
|
||||
valid_quest_list: {
|
||||
quest: K.ATTR({ index: '1' }, {}),
|
||||
},
|
||||
valid_course_list: {
|
||||
course: K.ATTR({ index: '1' }, {}),
|
||||
},
|
||||
});
|
||||
|
||||
const getPlayerDataOp3 = async (
|
||||
refid: string,
|
||||
_info: EamuseInfo,
|
||||
options?: { name?: string; isRegist?: boolean },
|
||||
) => {
|
||||
const p = await readProfile(refid);
|
||||
|
||||
if (options?.name && options.name.length > 0) {
|
||||
p.name = options.name;
|
||||
await writeProfile(refid, p);
|
||||
}
|
||||
|
||||
const identity = {
|
||||
name: K.ITEM('str', p.name || 'GUEST'),
|
||||
play_count: K.ITEM('s32', 0),
|
||||
today_play_count: K.ITEM('s32', 0),
|
||||
old_play_count: K.ITEM('s32', 0),
|
||||
old_recital_count: K.ITEM('s32', 0),
|
||||
};
|
||||
|
||||
if (options?.isRegist) {
|
||||
return {
|
||||
permitted_list,
|
||||
...op3RegistExtras(),
|
||||
...identity,
|
||||
music_list: op3FreshMusicFlags(),
|
||||
free_for_play_music_list: op3FreshMusicFlags(),
|
||||
last: buildOp3Last(p, true),
|
||||
travel: buildOp3Travel(p),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
permitted_list,
|
||||
...identity,
|
||||
music_list: op3FreshMusicFlags(),
|
||||
free_for_play_music_list: op3FreshMusicFlags(),
|
||||
last: buildOp3Last(p, false),
|
||||
travel: buildOp3Travel(p),
|
||||
extra_param: buildOp3ExtraParam(),
|
||||
};
|
||||
};
|
||||
|
||||
function buildLegacyPlayerPayload(p: Profile, version: NosVersionHelper, parts: any) {
|
||||
const {
|
||||
param,
|
||||
brooch,
|
||||
stairs,
|
||||
kentei_record,
|
||||
island_progress,
|
||||
correct_permitted_list,
|
||||
music_list,
|
||||
music_list2,
|
||||
} = parts;
|
||||
|
||||
return {
|
||||
name: K.ITEM('str', p.name),
|
||||
play_count: K.ITEM('s32', p.playCount),
|
||||
|
|
@ -227,7 +426,15 @@ export const regist_playdata: EPR = async (info, data, send) => {
|
|||
if (!refid) return send.deny();
|
||||
|
||||
const name = $(data).str('name');
|
||||
console.debug(`nos op2 regist: ${name}`);
|
||||
const version = new NosVersionHelper(info);
|
||||
console.debug(`nos ${version.version} regist: ${name}`);
|
||||
|
||||
if (version.isOp3()) {
|
||||
const payload = await getPlayerDataOp3(refid, info, { name, isRegist: true });
|
||||
console.log(`[nostalgia@asphyxia] op3 regist_playdata refid=${refid} name=${name}`);
|
||||
send.object(payload);
|
||||
return;
|
||||
}
|
||||
|
||||
send.object(await getPlayerData(refid, info, name));
|
||||
};
|
||||
|
|
@ -236,18 +443,142 @@ export const get_playdata: EPR = async (info, data, send) => {
|
|||
const refid = $(data).str('refid');
|
||||
if (!refid) return send.deny();
|
||||
|
||||
const version = new NosVersionHelper(info);
|
||||
if (version.isOp3()) {
|
||||
send.object(await getPlayerDataOp3(refid, info));
|
||||
return;
|
||||
}
|
||||
|
||||
send.object(await getPlayerData(refid, info));
|
||||
};
|
||||
|
||||
// export const set_stage_result: EPR = async (info, data, send) => {
|
||||
// return send.object();
|
||||
// };
|
||||
export const set_stage_result: EPR = async (info, data, send) => {
|
||||
const version = new NosVersionHelper(info);
|
||||
if (!version.isOp3()) {
|
||||
send.success();
|
||||
return;
|
||||
}
|
||||
|
||||
const refid = $(data).str('refid');
|
||||
if (!refid) return send.deny();
|
||||
|
||||
const scoreData = await readScores(refid);
|
||||
const stages = $(data).elements('stageinfo.stage');
|
||||
const stage = stages.length > 0 ? stages[stages.length - 1] : null;
|
||||
if (!stage) {
|
||||
send.success();
|
||||
return;
|
||||
}
|
||||
|
||||
const mid = stage.attr().music_index;
|
||||
const type = stage.attr().sheet_type;
|
||||
const common = stage.element('common');
|
||||
const key = `${mid}:${type}`;
|
||||
const o = _.get(scoreData, `scores.${key}`, {});
|
||||
const isHigh = common.number('score', 0) >= _.get(o, 'score', 0);
|
||||
|
||||
scoreData.scores[key] = {
|
||||
score: Math.max(common.number('score', 0), _.get(o, 'score', 0)),
|
||||
grade: isHigh ? common.number('grade', 0) : _.get(o, 'grade', 0),
|
||||
recital: _.get(o, 'recital', 0),
|
||||
mode: isHigh ? common.number('hands_mode', 0) : _.get(o, 'mode', 0),
|
||||
count: Math.max(common.number('play_count', 0), _.get(o, 'count', 1)),
|
||||
clear: common.number('clear_count', _.get(o, 'clear', 0)),
|
||||
multi: common.number('multi_count', _.get(o, 'multi', 0)),
|
||||
flag: Math.max(common.number('clear_flag', 0), _.get(o, 'flag', 0)),
|
||||
};
|
||||
|
||||
await writeScores(refid, scoreData);
|
||||
send.object({ player: {} });
|
||||
};
|
||||
|
||||
export const set_total_result: EPR = async (info, data, send) => {
|
||||
const refid = $(data).str('refid');
|
||||
if (!refid) return send.deny();
|
||||
|
||||
const isForte = new NosVersionHelper(info).isFirstOrForte()
|
||||
const version = new NosVersionHelper(info);
|
||||
if (version.isOp3()) {
|
||||
const p = await readProfile(refid);
|
||||
|
||||
p.playCount = $(data).number('play_count', p.playCount);
|
||||
p.todayPlayCount = $(data).number('today_play_count', p.todayPlayCount);
|
||||
p.oldPlayCount = $(data).number('old_play_count', p.oldPlayCount);
|
||||
p.oldRecitalCount = $(data).number('old_recital_count', p.oldRecitalCount);
|
||||
|
||||
const last = $(data).element('last');
|
||||
p.group = last.number('music_group', p.group);
|
||||
p.music = last.number('music_index', p.music);
|
||||
p.sheet = last.number('sheet_type', p.sheet);
|
||||
p.performType = last.number('perform_type', p.performType);
|
||||
p.filterFlag = Number(last.bigint('filter_flag') ?? BigInt(p.filterFlag));
|
||||
p.brooch = last.number('brooch_index', p.brooch);
|
||||
p.hispeed = last.number('hi_speed_level', p.hispeed);
|
||||
p.beatGuide = last.number('beat_guide', p.beatGuide);
|
||||
p.headphone = last.number('headphone_volume', p.headphone);
|
||||
p.judgeBar = last.number('judge_bar_pos', p.judgeBar);
|
||||
p.mode = last.number('hands_mode', p.mode);
|
||||
p.near = last.number('near_setting', p.near);
|
||||
p.offset = last.number('judge_delay_offset', p.offset);
|
||||
p.keyBeam = last.number('key_beam_level', p.keyBeam);
|
||||
p.orbit = last.number('orbit_type', p.orbit);
|
||||
p.noteHeight = last.number('note_height', p.noteHeight);
|
||||
p.noteWidth = last.number('note_width', p.noteWidth);
|
||||
p.judgeWidth = last.number('judge_width_type', p.judgeWidth);
|
||||
p.beatVolume = last.number('beat_guide_volume', p.beatVolume);
|
||||
p.beatType = last.number('beat_guide_type', p.beatType);
|
||||
p.keyVolume = last.number('key_volume_offset', p.keyVolume);
|
||||
p.bgmVolume = last.number('bgm_volume_offset', p.bgmVolume);
|
||||
p.note = last.number('note_disp_type', p.note);
|
||||
p.sf = last.number('slow_fast', p.sf);
|
||||
p.optionSetting = last.number('option_setting', p.optionSetting);
|
||||
p.judgeFX = last.number('judge_effect_adjust', p.judgeFX);
|
||||
p.simple = last.number('simple_bg', p.simple);
|
||||
p.bingo = last.number('bingo_index', p.bingo);
|
||||
p.classBasic = last.number('class_basic', p.classBasic);
|
||||
p.classRecital = last.number('class_recital', p.classRecital);
|
||||
p.gradeBasic = last.number('grade_basic', p.gradeBasic);
|
||||
p.gradeRecital = last.number('grade_recital', p.gradeRecital);
|
||||
|
||||
p.money = $(data).number('travel.money', p.money);
|
||||
p.pianistPower = $(data).number('travel.pianist_power', p.pianistPower);
|
||||
p.fameId = $(data).number('travel.fame_index', p.fameId);
|
||||
p.kingdomId = $(data).number('travel.kingdom_id', p.kingdomId);
|
||||
p.questIndex = $(data).number('travel.quest_index', p.questIndex);
|
||||
|
||||
let flags = _.get($(data).obj, 'music_list.flag', []);
|
||||
if (!_.isArray(flags)) flags = [flags];
|
||||
for (const flag of flags) {
|
||||
const sheet = _.get(flag, '@attr.sheet_type', -1);
|
||||
if (sheet == '0') p.musicList.type_0 = _.get(flag, '@content', p.musicList.type_0);
|
||||
else if (sheet == '1') p.musicList.type_1 = _.get(flag, '@content', p.musicList.type_1);
|
||||
else if (sheet == '2') p.musicList.type_2 = _.get(flag, '@content', p.musicList.type_2);
|
||||
else if (sheet == '3') p.musicList.type_3 = _.get(flag, '@content', p.musicList.type_3);
|
||||
}
|
||||
|
||||
let freeFlags = _.get($(data).obj, 'free_for_play_music_list.flag', []);
|
||||
if (!_.isArray(freeFlags)) freeFlags = [freeFlags];
|
||||
for (const flag of freeFlags) {
|
||||
const sheet = _.get(flag, '@attr.sheet_type', -1);
|
||||
if (sheet == '0') p.musicList2.type_0 = _.get(flag, '@content', p.musicList2.type_0);
|
||||
else if (sheet == '1') p.musicList2.type_1 = _.get(flag, '@content', p.musicList2.type_1);
|
||||
else if (sheet == '2') p.musicList2.type_2 = _.get(flag, '@content', p.musicList2.type_2);
|
||||
else if (sheet == '3') p.musicList2.type_3 = _.get(flag, '@content', p.musicList2.type_3);
|
||||
}
|
||||
|
||||
let params = $(data).elements('extra_param.param');
|
||||
for (const param of params) {
|
||||
const type = param.attr().type;
|
||||
const parray = param.numbers('params_array');
|
||||
if (type == null || parray == null) continue;
|
||||
p.params[type] = parray;
|
||||
}
|
||||
|
||||
await writeProfile(refid, p);
|
||||
send.success();
|
||||
return;
|
||||
}
|
||||
|
||||
const isForte = version.isFirstOrForte()
|
||||
const p = await readProfile(refid);
|
||||
|
||||
p.playCount = $(data).number('play_count', p.playCount);
|
||||
|
|
@ -471,6 +802,38 @@ export const get_musicdata: EPR = async (info, data, send) => {
|
|||
const version = new NosVersionHelper(info)
|
||||
const scoreData = await readScores(refid);
|
||||
|
||||
if (version.isOp3()) {
|
||||
const music: any[] = [];
|
||||
for (const m in scoreData.scores) {
|
||||
const mdata = m.split(':');
|
||||
const musi = scoreData.scores[m];
|
||||
if (parseInt(mdata[0], 10) > version.getMusicMaxIndex()) continue;
|
||||
|
||||
const chart = {
|
||||
score: K.ITEM('s32', musi.score),
|
||||
play_count: K.ITEM('s32', musi.count),
|
||||
clear_count: K.ITEM('s32', musi.clear),
|
||||
multi_count: K.ITEM('s32', musi.multi),
|
||||
clear_flag: K.ITEM('s32', musi.flag),
|
||||
hands_mode: K.ITEM('s8', musi.mode),
|
||||
evaluation: K.ITEM('u32', 5),
|
||||
grade: K.ITEM('u32', musi.grade),
|
||||
};
|
||||
|
||||
music.push(K.ATTR({
|
||||
music_index: mdata[0],
|
||||
sheet_type: mdata[1],
|
||||
}, {
|
||||
recital: chart,
|
||||
...chart,
|
||||
}));
|
||||
}
|
||||
|
||||
console.log(`[nostalgia@asphyxia] op3 get_musicdata refid=${refid} music=${music.length}`);
|
||||
send.object({ music });
|
||||
return;
|
||||
}
|
||||
|
||||
const recital_record: any[] = [];
|
||||
const music: any[] = [];
|
||||
|
||||
|
|
@ -520,9 +883,42 @@ export const get_musicdata: EPR = async (info, data, send) => {
|
|||
});
|
||||
};
|
||||
|
||||
function normalizeProfile(profile?: Profile | null): Profile {
|
||||
if (!profile) {
|
||||
return { ...defaultProfile };
|
||||
}
|
||||
|
||||
return padOp3MusicLists({
|
||||
...defaultProfile,
|
||||
...profile,
|
||||
params: {
|
||||
...defaultProfile.params,
|
||||
...(profile.params || {}),
|
||||
},
|
||||
musicList: {
|
||||
...defaultProfile.musicList,
|
||||
...(profile.musicList || {}),
|
||||
},
|
||||
musicList2: {
|
||||
...defaultProfile.musicList2,
|
||||
...(profile.musicList2 || {}),
|
||||
},
|
||||
brooches: {
|
||||
...defaultProfile.brooches,
|
||||
...(profile.brooches || {}),
|
||||
},
|
||||
islands: profile.islands || {},
|
||||
kentei: profile.kentei || {},
|
||||
cat_stairs: {
|
||||
...defaultProfile.cat_stairs,
|
||||
...(profile.cat_stairs || {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function readProfile(refid: string): Promise<Profile> {
|
||||
const profile = await DB.FindOne<Profile>(refid, { collection: 'profile' })
|
||||
return profile || defaultProfile
|
||||
const profile = await DB.FindOne<Profile>(refid, { collection: 'profile' });
|
||||
return normalizeProfile(profile);
|
||||
}
|
||||
|
||||
async function writeProfile(refid: string, profile: Profile) {
|
||||
|
|
@ -546,7 +942,7 @@ const defaultProfile: Profile = {
|
|||
sheet: 0,
|
||||
brooch: 0,
|
||||
hispeed: 0,
|
||||
beatGuide: 1,
|
||||
beatGuide: 0,
|
||||
headphone: 0,
|
||||
judgeBar: 250,
|
||||
group: 0,
|
||||
|
|
@ -560,8 +956,8 @@ const defaultProfile: Profile = {
|
|||
keyBeam: 0,
|
||||
orbit: 0,
|
||||
noteHeight: 10,
|
||||
noteWidth: 0,
|
||||
judgeWidth: 0,
|
||||
noteWidth: 10,
|
||||
judgeWidth: 10,
|
||||
beatVolume: 0,
|
||||
beatType: 0,
|
||||
keyVolume: 0,
|
||||
|
|
@ -574,6 +970,18 @@ const defaultProfile: Profile = {
|
|||
fame: 0,
|
||||
fameId: 0,
|
||||
island: 0,
|
||||
performType: 0,
|
||||
filterFlag: 0,
|
||||
optionSetting: 0,
|
||||
classBasic: 0,
|
||||
classRecital: 0,
|
||||
gradeBasic: 0,
|
||||
gradeRecital: 0,
|
||||
pianistPower: 0,
|
||||
kingdomId: 0,
|
||||
questIndex: 0,
|
||||
oldPlayCount: 0,
|
||||
oldRecitalCount: 0,
|
||||
brooches: {
|
||||
'1': {
|
||||
level: 1,
|
||||
|
|
@ -593,7 +1001,8 @@ const defaultProfile: Profile = {
|
|||
}
|
||||
},
|
||||
params: {
|
||||
'1': [0],
|
||||
'1': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
'2': [64, 0, 0, 0, 0, 0, 0, 0],
|
||||
},
|
||||
musicList: {
|
||||
type_0: Array(32).fill(-1),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { get_common_info, get_music_info } from "./handler/common";
|
||||
import { get_musicdata, get_playdata, regist_playdata, set_total_result } from "./handler/player"
|
||||
import { get_musicdata, get_playdata, regist_playdata, set_stage_result, set_total_result } from "./handler/player"
|
||||
import { fixIndexBug } from "./handler/webui";
|
||||
|
||||
export function register() {
|
||||
|
|
@ -11,6 +11,7 @@ export function register() {
|
|||
// Helper for register multiple versions.
|
||||
R.Route(method, handler); // First version and Forte.
|
||||
R.Route(`op2_${method}`, handler);
|
||||
R.Route(`op3_${method}`, handler);
|
||||
};
|
||||
|
||||
const CommonRoute = (method: string, handler: EPR | boolean) =>
|
||||
|
|
@ -19,6 +20,9 @@ export function register() {
|
|||
const PlayerRoute = (method: string, handler: EPR | boolean) =>
|
||||
MultiRoute(`player.${method}`, handler)
|
||||
|
||||
const PcbRoute = (method: string, handler: EPR | boolean) =>
|
||||
MultiRoute(`pcb.${method}`, handler)
|
||||
|
||||
// Common
|
||||
CommonRoute('get_common_info', get_common_info);
|
||||
CommonRoute('get_music_info', get_music_info);
|
||||
|
|
@ -29,8 +33,10 @@ export function register() {
|
|||
PlayerRoute('regist_playdata', regist_playdata)
|
||||
PlayerRoute('set_total_result', set_total_result)
|
||||
|
||||
//TODO: Fix this things with actual working handler.
|
||||
PlayerRoute('set_stage_result', true)
|
||||
PlayerRoute('set_stage_result', set_stage_result)
|
||||
|
||||
// Test Menu
|
||||
PcbRoute('report_testmode_settings', true)
|
||||
|
||||
R.Unhandled(async (info, data, send) => {
|
||||
if (["eventlog"].includes(info.module)) return;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,18 @@ export interface Profile {
|
|||
fame: number;
|
||||
fameId: number;
|
||||
island: number;
|
||||
performType: number;
|
||||
filterFlag: number;
|
||||
optionSetting: number;
|
||||
classBasic: number;
|
||||
classRecital: number;
|
||||
gradeBasic: number;
|
||||
gradeRecital: number;
|
||||
pianistPower: number;
|
||||
kingdomId: number;
|
||||
questIndex: number;
|
||||
oldPlayCount: number;
|
||||
oldRecitalCount: number;
|
||||
params: {
|
||||
[key: string]: number[];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,4 +44,8 @@ export class NosVersionHelper {
|
|||
isFirstOrForte() {
|
||||
return this.version === 'First' || this.version === 'Forte'
|
||||
}
|
||||
|
||||
isOp3() {
|
||||
return this.version === 'Op3'
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user