Merge pull request #77 from dannylin0711/stable

SDVX EG Final
This commit is contained in:
Freddie W 2026-01-22 16:27:11 +08:00 committed by GitHub
commit 910c134f12
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
229 changed files with 14376 additions and 5873 deletions

View File

@ -1,10 +1,12 @@
# SOUND VOLTEX
Plugin Version: **v6.1.2**
Plugin Version: **v6.2.0**
## Provide out of box usable exprience, everything is unlocked and good to go.
Prerequisite CORE version: v1.50c or above
# Make sure to set SDVX installation path in CORE settings first. The Webui will load assets directly from the game installation folder.
Prerequisite CORE version: v1.50d or above
Supported Versions:
@ -28,11 +30,13 @@ If you want to help with the plugin, you can open pull request.
This version save data is not compatible with some forks plugin, please use it with caution if you already uses an unsupported version.
Remember to import asset from the game files first time when using webui.
Change Log
===========
## 6.2.0
1. Support EG Final 2025120900.
## 6.1.2
1. Hotfix for 神 skill analyzer not showing after passed.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
import { EVENT6, COURSES6, EXTENDS6, VALGENE6 } from '../data/exg';
import {getVersion, getRandomIntInclusive} from '../utils';
import fs from 'fs';
import { get } from 'http';
import { EXG } from '../data/exg';
import {getVersion, getRandomIntInclusive, getCurrentWeekOfYear, SeededRandom, getWeekStartAndEnd} from '../utils';
import * as fs from 'fs';
export const informationString =
`[sz:120] [olc:555555][ol:4][c:ff3333,3333ff,77ff77]Asphyxia
@ -8,7 +9,7 @@ export const informationString =
[sz:30][sz:30][c:ffffff,888888]
[c:00d5ff,888888]ASPHYXIA CORE ${CORE_VERSION}
[c:e5f3ff,a3d5ff]SDVX Plugin ver 6.1.0
[c:e5f3ff,a3d5ff]SDVX Plugin ver 6.2.0
[f:0][c:ff3333,ffffff]FREE SOFTWARE. BEWARE OF SCAMMERS.
@ -33,17 +34,32 @@ export const common: EPR = async (info, data, send) => {
let courses = [];
let extend = [];
console.log("Calling common function");
let exg_data_json = JSON.parse(fs.readFileSync('./plugins/sdvx@asphyxia/data/exg_data.json', 'utf8'));
events = EVENT6;
courses = COURSES6;
// EXTENDS6.forEach(val => extend.push(Object.assign({}, val)));
extend = EXTENDS6;
extend = extend.concat(exg_data_json.extends_data);
// extend = extend.concat(exg_data.extends_data);
let versionCommonObject: any = {
EVENT: [],
COURSE: [],
EXTEND: [],
VALGENE: {},
};
switch(info.method){
case 'sv6_common': {
versionCommonObject = EXG;
break;
}
}
events = events.concat(versionCommonObject.EVENT);
courses = courses.concat(versionCommonObject.COURSE);
extend = extend.concat(versionCommonObject.EXTEND);
let songs = [];
if (U.GetConfig('enblae_VSync')) {
console.log("Enabling VSync");
events.push('SUBMONITOR_VSYNC_ENABLE');
}
if (U.GetConfig('unlock_all_songs')) {
console.log("Unlocking songs");
let songNum = U.GetConfig('music_count');
@ -56,6 +72,14 @@ export const common: EPR = async (info, data, send) => {
});
}
if(i == 636){ // Everlasting Message ULT
songs.push({
music_id: K.ITEM('s32', i),
music_type: K.ITEM('u8', 5),
limited: K.ITEM('u8', 3),
});
}
}
}
@ -144,7 +168,7 @@ export const common: EPR = async (info, data, send) => {
}
}
if(Math.abs(getVersion(info)) == 6){
if(getVersion(info) == 6){
extend.push({
id: 3,
type: 1,
@ -182,6 +206,17 @@ export const common: EPR = async (info, data, send) => {
time.setDate(tempDate);
const newTime = time.getTime();
console.log(getCurrentWeekOfYear());
const seed = parseInt(`${new Date().getFullYear()}${getCurrentWeekOfYear()}`);
const rng = new SeededRandom(seed);
// const weekly_music_id = rng.next() % U.GetConfig('music_count');
const weekly_music_id = 636
console.log(getWeekStartAndEnd().endOfWeek);
console.log(getWeekStartAndEnd().startOfWeek);
console.log("Sending common objects");
send.object(
{
@ -257,10 +292,10 @@ export const common: EPR = async (info, data, send) => {
season: K.ITEM('s32',3),
rule: K.ITEM('s32',0),
rank_match_target: K.ARRAY('s32', [
2,2,2,2,
2,2,2,2,
1,1,1,1,
1,1,1,1,
0,1,2,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
@ -274,18 +309,24 @@ export const common: EPR = async (info, data, send) => {
is_shop: K.ITEM('bool',true)
},
valgene: {
info: unlock_codes.map(v => ({
valgene_name: K.ITEM('str', 'VALKYRIE GENERATOR VOL.' + v),
valgene_name_english: K.ITEM('str', 'VALKYRIE GENERATOR VOL.' + v),
valgene_id: K.ITEM('s32', v),
info: versionCommonObject.VALGENE.info.map(v => ({
valgene_name: K.ITEM('str', v.valgene_name),
valgene_name_english: K.ITEM('str', v.valgene_name_english),
valgene_id: K.ITEM('s32', v.valgene_id),
})),
catalog: VALGENE6.catalog.map(c => ({
catalog: versionCommonObject.VALGENE.catalog.map(c => ({
valgene_id: K.ITEM('s32', c.valgene_id),
rarity: K.ITEM('s32', c.rarity),
item_type: K.ITEM('s32', c.item_type),
item_id: K.ITEM('s32', c.item_id),
})),
},
weekly_music: {
week_id: K.ITEM('s32',0),
music_id: K.ITEM('s32', weekly_music_id),
time_start: K.ITEM('u64',BigInt(getWeekStartAndEnd().startOfWeek)),
time_end: K.ITEM('u64',BigInt(getWeekStartAndEnd().endOfWeek-999)),
},
invest:{
limit_date: K.ITEM('u64',BigInt(newTime)),
}

View File

@ -7,19 +7,26 @@ import { MatchingRoom } from '../models/matching';
let Tracker:MatchingRoom[] = [];
export const hiscore: EPR = async (info, data, send) => {
const records = await DB.Find<MusicRecord>(null, { collection: 'music' });
const version = getVersion(info);
let limit = $(data).number('limit');
let offset = $(data).number('offset');
const profiles = _.groupBy(
await DB.Find<Profile>(null, { collection: 'profile' }),
'__refid'
);
console.log(`Sending hiscore limit:${limit} offset:${offset}`);
let filtered_records = records.filter(r => r.mid > 0+offset && r.mid <= 0+offset+limit);
return send.object({
sc: {
d: _.map(
_.groupBy(records, r => {
_.groupBy(filtered_records, r => {
return `${r.mid}:${r.type}`;
}),
r => _.maxBy(r, 'score')
@ -39,7 +46,10 @@ export const hiscore: EPR = async (info, data, send) => {
lx_nm: K.ITEM('str', profiles[r.__refid][0].name),
lx_sc: K.ITEM('u32', r.exscore ?? 0),
avg_sc: K.ITEM('u32', r.score),
cr: K.ITEM('s32', 8763)
avg_ex: K.ITEM('u32', r.exscore ?? 0),
cr: K.ITEM('s32', 8763),
avg_sc_lv: K.ARRAY('u32', Array.from({length:13}).map(x => r.score)), //Array.from({length:12}).map(x=> r.score)
avg_ex_lv: K.ARRAY('u32', Array.from({length:13}).map(x => r.exscore))
})),
},
});

View File

@ -42,7 +42,7 @@ function unlock_all_valkgen(items: Partial<Item>[]) {
export const loadScore: EPR = async (info, data, send) => {
console.log("Now loading score");
const version = Math.abs(getVersion(info));
const version = getVersion(info);
console.log("Got version:" + version);
let refid = $(data).str('refid', $(data).attr().dataid);
if (version === 2) refid = $(data).str('dataid', '0');
@ -52,11 +52,10 @@ export const loadScore: EPR = async (info, data, send) => {
console.log('Finding record');
const records = await DB.Find<MusicRecord>(refid, { collection: 'music' });
return send.object({
music: {
info: records.map(r => ({
param: K.ARRAY('u32', [
info: records.map(r => {
let tempArr = [
r.mid,
r.type,
r.score,
@ -68,7 +67,7 @@ export const loadScore: EPR = async (info, data, send) => {
r.buttonRate,
r.longRate,
r.volRate,
0,
r.volforce ? r.volforce : 0,
0,
0,
0,
@ -79,8 +78,12 @@ export const loadScore: EPR = async (info, data, send) => {
0,
0,
0,
]),
})),
];
return {
param: K.ARRAY('u32', tempArr),
}
}),
},
});
@ -111,8 +114,18 @@ export const saveScore: EPR = async (info, data, send) => {
buttonRate: 0,
longRate: 0,
volRate: 0,
volforce: 0,
judge: [],
};
if (record.judge.length == 0) {
console.log("No judge data found, save them for the first time so use current data as baseline.");
record.judge = i.numbers('judge', []);
if (record.judge.length == 7) {
console.log("Judge data length is valid with s-crit.");
}
}
const score = i.number('score', 0);
const exscore = i.number('exscore', 0);
if (score > record.score) {
@ -121,11 +134,42 @@ export const saveScore: EPR = async (info, data, send) => {
record.longRate = i.number('long_rate', 0);
record.volRate = i.number('vol_rate', 0);
}
if (exscore > record.exscore) {
record.exscore = exscore;
}
record.clear = Math.max(i.number('clear_type', 0), record.clear);
if (score >= record.score || exscore >= record.exscore) {
const newJudge = i.numbers('judge', []);
if (newJudge.length == record.judge.length) {
for (let j = 0; j < record.judge.length; j++) {
if (newJudge[j] > record.judge[j]) {
record.judge[j] = newJudge[j];
}
}
}
}
const volforce = i.number('volforce', 0);
if (isNaN(record.volforce) || record.volforce === null) {
console.log("Old Volforce is NaN or null, setting to 0");
record.volforce = 0;
}
if (volforce > record.volforce) {
record.volforce = volforce;
}
if(i.number('clear_type', 0) == 6 && record.clear >= 4){
console.log("Detected Maxxive Clear, but originally UC or PUC, no override.")
}else{
record.clear = Math.max(i.number('clear_type', 0), record.clear);
}
record.grade = Math.max(i.number('score_grade', 0), record.grade);
@ -201,12 +245,20 @@ export const save: EPR = async (info, data, send) => {
effCLeft: $(data).number('eff_c_left'),
effCRight: $(data).number('eff_c_right'),
narrowDown: $(data).number('narrow_down'),
vGateOverRadar: $(data).element('variant_gate').numbers("over_radar")
},
$inc: {
packets: $(data).number('earned_gamecoin_packet'),
blocks: $(data).number('earned_gamecoin_block'),
blasterEnergy: $(data).number('earned_blaster_energy'),
extrackEnergy: $(data).number('earned_extrack_energy'),
vGatePower: $(data).element('variant_gate').number('earned_power'),
vGateNotes: $(data).element('variant_gate').element('earned_element').number('notes'),
vGatePeak: $(data).element('variant_gate').element('earned_element').number('peak'),
vGateTsumami: $(data).element('variant_gate').element('earned_element').number('tsumami'),
vGateTricky: $(data).element('variant_gate').element('earned_element').number('tricky'),
vGateOnehand: $(data).element('variant_gate').element('earned_element').number('onehand'),
vGateHandtrip: $(data).element('variant_gate').element('earned_element').number('handtrip'),
},
}
);
@ -385,6 +437,35 @@ export const load: EPR = async (info, data, send) => {
profile.appeal_frame = profile.appeal_frame ? profile.appeal_frame : 0;
profile.support_team = profile.support_team ? profile.support_team : 0;
profile.use_pro_team = profile.use_pro_team ? profile.use_pro_team : false;
profile.vGatePower = profile.vGatePower ? profile.vGatePower : 0;
profile.vGateNotes = profile.vGateNotes ? profile.vGateNotes : 0;
profile.vGatePeak = profile.vGatePeak ? profile.vGatePeak : 0;
profile.vGateTsumami = profile.vGateTsumami ? profile.vGateTsumami : 0;
profile.vGateTricky = profile.vGateTricky ? profile.vGateTricky : 0;
profile.vGateOnehand = profile.vGateOnehand ? profile.vGateOnehand : 0;
profile.vGateHandtrip = profile.vGateHandtrip ? profile.vGateHandtrip : 0;
profile.vGateOverRadar = profile.vGateOverRadar ? profile.vGateOverRadar : [];
if(!profile.vGatePower){ // Data migration
await DB.Update<Profile>(
refid,
{ collection: 'profile' },
{
$set: {
vGatePower: 0,
vGateNotes: 0,
vGatePeak: 0,
vGateTsumami: 0,
vGateTricky: 0,
vGateOnehand: 0,
vGateHandtrip: 0,
vGateOverRadar: [],
}
}
)
}
return send.pugFile('templates/load.pug', {
courses,
@ -435,6 +516,16 @@ export const create: EPR = async (info, data, send) => {
blasterCount: 0,
blasterEnergy: 0,
extrackEnergy: 0,
vGatePower: 0,
vGateNotes: 0,
vGatePeak: 0,
vGateTsumami: 0,
vGateTricky: 0,
vGateOnehand: 0,
vGateHandtrip: 0,
vGateOverRadar: [],
bgm: 0,
subbg: 0,
nemsys: 0,
@ -449,6 +540,7 @@ export const create: EPR = async (info, data, send) => {
mainbg: 0,
appeal_frame: 0,
support_team: 0,
use_pro_team: false,
headphone: 0,
musicID: 0,
@ -456,6 +548,7 @@ export const create: EPR = async (info, data, send) => {
sortType: 0,
expPoint: 0,
mUserCnt: 0,
boothFrame: [0, 0, 0, 0, 0]
};

View File

@ -5,9 +5,15 @@ import { getVersion, IDToCode, GetCounter } from '../utils';
import { Mix } from '../models/mix';
import { fstat } from 'fs';
import { error } from 'console';
import { setMaxIdleHTTPParsers } from 'http';
import { unpackS3P } from '../s3p';
import { secureHeapUsed } from 'crypto';
import { music_db } from '..';
import { zipFolderToFile } from '../utils/zip';
import path from 'path';
const joinUnder = (basePath: string, childPath: string) => {
const sanitizedChild = (childPath ?? '').replace(/^[/\\]+/, '');
return path.join(basePath, sanitizedChild);
};
export const updateProfile = async (data: {
refid: string;
@ -28,6 +34,7 @@ export const updateProfile = async (data: {
mainbg?: string;
appeal_frame?: string;
support_team?: string;
use_pro_team?: string;
}) => {
if (data.refid == null) return;
@ -121,6 +128,16 @@ export const updateProfile = async (data: {
if (!_.isNaN(validMainbg)) update.mainbg = validMainbg;
}
console.log(data.use_pro_team);
if (data.use_pro_team !== undefined && data.use_pro_team == "on") {
const validUseProTeam = true;
update.use_pro_team = validUseProTeam;
} else {
const validUseProTeam = false;
update.use_pro_team = validUseProTeam;
}
await DB.Update<Profile>(
data.refid,
{ collection: 'profile' },
@ -140,10 +157,6 @@ export const updateMix = async (data: {
}
if (data.creator && data.creator.length > 0) {
// const validCreator = data.creator
// .toUpperCase()
// .replace(/[^ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?#$&*\-\.\ ]/g, '')
// .slice(0, 8);
if (data.creator.length > 0) update.creator = data.creator;
}
@ -237,46 +250,46 @@ export const import_assets = async (data: { path: string }, send: WebUISend) =>
// await init(wasmUrl);
// let ffmpeg = await Wasmer.fromRegistry("wasmer/ffmpeg");
let Admzip = require('../../_shared/lib/adm-zip')
let path = data.path
console.log(path)
const sdvxInstallPath = data.path;
console.log(sdvxInstallPath)
let fs = require('fs')
if (!fs.existsSync(path + '/data/graphics/')) {
const graphicsDir = path.join(sdvxInstallPath, 'data', 'graphics');
if (!fs.existsSync(graphicsDir)) {
console.log('Path for Graphics does not exist.')
send.error(400,'Path for Graphics does not exist.')
return
}
await fs.promises.cp(path + "/data/graphics/ap_card", './plugins/sdvx@asphyxia/webui/asset/ap_card', {recursive: true}).catch((err: any) => {
await fs.promises.cp(path.join(graphicsDir, 'ap_card'), './plugins/sdvx@asphyxia/webui/asset/ap_card', {recursive: true}).catch((err: any) => {
console.log(err)
})
await fs.promises.cp(path + "/data/graphics/chat_stamp", './plugins/sdvx@asphyxia/webui/asset/chat_stamp', {recursive: true}).catch((err: any) => {
await fs.promises.cp(path.join(graphicsDir, 'chat_stamp'), './plugins/sdvx@asphyxia/webui/asset/chat_stamp', {recursive: true}).catch((err: any) => {
console.log(err)
})
await fs.promises.cp(path + "/data/graphics/game_nemsys", './plugins/sdvx@asphyxia/webui/asset/nemsys', {recursive: true}).catch((err: any) => {
await fs.promises.cp(path.join(graphicsDir, 'game_nemsys'), './plugins/sdvx@asphyxia/webui/asset/nemsys', {recursive: true}).catch((err: any) => {
console.log(err)
})
await fs.promises.cp(path + "/data/graphics/submonitor_bg", './plugins/sdvx@asphyxia/webui/asset/submonitor_bg', {recursive: true}).catch((err: any) => {
await fs.promises.cp(path.join(graphicsDir, 'submonitor_bg'), './plugins/sdvx@asphyxia/webui/asset/submonitor_bg', {recursive: true}).catch((err: any) => {
console.log(err)
})
if (!fs.existsSync(path + '/data/sound/')) {
const soundDir = path.join(sdvxInstallPath, 'data', 'sound');
if (!fs.existsSync(soundDir)) {
console.log('Path for sound does not exist.')
send.error(400,'Path for sound does not exist.')
return
}
let zip = new Admzip()
await fs.promises.readdir(path + "/data/sound/custom").then((files: any) => {
// let file = files[0]
console.log(files)
const customSoundDir = path.join(soundDir, 'custom');
const files = await fs.promises.readdir(customSoundDir)
console.log(files)
for(let i in files){
let file = files[i]
if(file.endsWith('.s3p')){
fs.mkdirSync('./plugins/sdvx@asphyxia/webui/asset/temp/'+file, { recursive: true });
// fs.mkdirSync('./plugins/sdvx@asphyxia/webui/asset/audio/'+file.substring(0, 9), { recursive: true });
unpackS3P('./plugins/sdvx@asphyxia/webui/asset/temp/'+file, path + "/data/sound/custom/" + file, {})
for (const file of files) {
if (file.endsWith('.s3p')) {
fs.mkdirSync('./plugins/sdvx@asphyxia/webui/asset/temp/' + file, { recursive: true });
// fs.mkdirSync('./plugins/sdvx@asphyxia/webui/asset/audio/'+file.substring(0, 9), { recursive: true });
unpackS3P('./plugins/sdvx@asphyxia/webui/asset/temp/' + file, path.join(customSoundDir, file), {})
// fs.promises.readFileSync('./plugins/sdvx@asphyxia/webui/asset/temp/'+file+'/0.wma').then(async (data: any) => {
// const instance = await ffmpeg.entrypoint.run({
// args: ["-i", "-", "-f", "wav", "-"],
@ -299,16 +312,13 @@ export const import_assets = async (data: { path: string }, send: WebUISend) =>
// console.log(err)
// })
// }
}
}
}
}).finally(() => {
zip.addLocalFolder('./plugins/sdvx@asphyxia/webui/asset/temp', 'temp')
zip.writeZip('./plugins/sdvx@asphyxia/webui/asset/temp.zip')
await zipFolderToFile({
sourceDir: './plugins/sdvx@asphyxia/webui/asset/temp',
outZipPath: './plugins/sdvx@asphyxia/webui/asset/temp.zip',
rootInZip: 'temp',
})
await fs.promises.rm('./plugins/sdvx@asphyxia/webui/asset/temp', { recursive: true, force: true }).catch((err: any) => {
@ -465,4 +475,45 @@ export const update_webui_bgm_data = async (data: any, send: WebUISend) => {
fs.writeFileSync("./plugins/sdvx@asphyxia/webui/asset/json/data.json", JSON.stringify(datajson, null, 2))
send.json({status:"ok"})
}
export const update_music_db = async (data: any, send: WebUISend) => {
const fs = require('fs')
data = JSON.parse(U.parseXML(data.file, false))
fs.writeFileSync("./plugins/sdvx@asphyxia/webui/asset/json/music_db.json", JSON.stringify(data, null, 2))
}
export const sendMdb = async (data: any, send: WebUISend) => {
console.log('Sending music_db to WebUI...')
send.json(music_db)
}
export const sendAssetData = async ( data: { path: string }, send: WebUISend) => {
if (U.GetConfig('sdvx_path') == '') {
send.error(400, 'SDVX Path is not set in the plugin configuration.');
return;
}
let sdvx_path = U.GetConfig('sdvx_path');
const full_path = joinUnder(sdvx_path, data.path);
const fs = require('fs');
if (!fs.existsSync(full_path)) {
send.error(404, 'File not found');
return;
}
try {
const asset_data: Buffer = await fs.promises.readFile(full_path);
console.log(asset_data.length);
console.log('Sending asset file: ' + full_path);
send.buffer(asset_data);
} catch (e) {
console.log(e);
send.error(500, 'Failed to read file');
}
}

View File

@ -1,8 +1,5 @@
import {common,log} from './handlers/common';
import {hiscore, rival, saveMix, loadMix, globalMatch} from './handlers/features';
// import {} from './handlers/sv4/';
// import {} from './handlers/sv5/';
// import {} from './handlers/sv6/';
import {
updateProfile,
updateMix,
@ -13,7 +10,10 @@ import {
update_webui_nemsys_data,
update_webui_stamp_data,
update_webui_subbg_data,
update_webui_bgm_data
update_webui_bgm_data,
update_music_db,
sendMdb,
sendAssetData,
// sendImg,
// sendImgWithID,
// getScore,
@ -31,24 +31,46 @@ import {
print,
} from './handlers/profiles';
import { TRANSLATION_TABLE } from './utils';
import { MusicRecord } from './models/music_record';
enum Version{
Booth = 'game.',
II = 'game_2.',
GW = 'game_3.',
HH = 'game.sv4_',
VW = 'game.sv5_',
EG = 'game.sv6_',
}
import path from 'path';
export let music_db;
function load_music_db(){
IO.ReadFile('./webui/asset/json/music_db.json',{encoding:'utf8'}).then(data => {
music_db = JSON.parse(data);
const fs = require('fs');
if (U.GetConfig('sdvx_path') == '') {
console.log('sdvx_path is not set, skipping music_db load');
return;
}
let sdvx_path = U.GetConfig('sdvx_path');
const mdb_path = path.join(sdvx_path, 'data', 'others', 'music_db.xml');
fs.promises.readFile(mdb_path).then(data => {
let mdb_buffer = U.DecodeString(data, 'shift_jis');
music_db = U.parseXML(mdb_buffer, false);
console.log('music_db loaded, total: '+music_db.mdb.music.length);
music_db.mdb.music.forEach((m: any) => {
let title_name = m.info.title_name["@content"];
for(let [key, value] of Object.entries(TRANSLATION_TABLE)){
title_name = title_name.replaceAll(key, value);
}
m.info.title_name["@content"] = title_name;
})
})
.catch((err: any) => {
console.error('Error reading music_db.xml:', err);
});
}
export function register() {
@ -56,6 +78,7 @@ export function register() {
R.Contributor("LatoWolf");
R.GameCode('KFC');
R.Config('enable_VSync', { type: 'boolean', default: false, name:'Enable VSync'} );
R.Config('unlock_all_songs', { type: 'boolean', default: false, name:'Unlock All Songs'});
R.Config('unlock_all_navigators', { type: 'boolean', default: false, name:'Unlock All Navigators'} );
R.Config('unlock_all_appeal_cards', { type: 'boolean', default: false, name:'Unlock All Appeal Cards'});
@ -63,7 +86,9 @@ export function register() {
R.Config('use_asphyxia_gameover',{ type: 'boolean', default: true, name:'Use Asphyxia Gameover', desc:'Enable the Asphyxia gameover message after ending the game.'})
R.Config('use_blasterpass',{ type: 'boolean', default: true, name:'Use Blaster Pass', desc:'Enable Blaster Pass for VW and EG'});
R.Config('new_year_special',{ type: 'boolean', default: true, name:'Use New Year Special', desc:'Enable New Year Special BGM for login'});
R.Config('music_count',{ type: 'integer', default: 2200, name:'Music Count', desc:'The maximum id of music in the game.'});
R.Config('music_count',{ type: 'integer', default: 2500, name:'Music Count', desc:'The maximum id of music in the game.'});
R.Config('sdvx_path', { type: 'string', default: '', name:'SDVX Path', desc:'Path to your SDVX installation folder.'});
R.WebUIEvent('updateProfile', updateProfile);
R.WebUIEvent('updateMix', updateMix);
@ -75,6 +100,9 @@ export function register() {
R.WebUIEvent('update_webui_chat_stamp', update_webui_stamp_data);
R.WebUIEvent('update_webui_subbg', update_webui_subbg_data);
R.WebUIEvent('update_webui_bgm', update_webui_bgm_data);
R.WebUIEvent('update_music_db', update_music_db);
R.WebUIEvent('getMusicDB', sendMdb);
R.WebUIEvent('getAssetData', sendAssetData);
const MultiRoute = (method: string, handler: EPR | boolean) => {
R.Route(`game.sv6_${method}`, handler);
@ -119,7 +147,7 @@ export function register() {
MultiRoute('log',log);
R.Route('eventlog.write', (_, __, send) => send.object({
gamesession: K.ITEM('s64', BigInt(1)),
gamesession: K.ITEM('s64', 1n),
logsendflg: K.ITEM('s32', 0),
logerrlevel: K.ITEM('s32', 0),
evtidnosendflg: K.ITEM('s32', 0)
@ -140,4 +168,8 @@ export function register() {
R.Unhandled();
load_music_db();
}

View File

@ -10,4 +10,7 @@ export interface MusicRecord {
buttonRate: number;
longRate: number;
volRate: number;
volforce: number;
judge: number[];
}

View File

@ -19,6 +19,16 @@ export interface Profile {
sortType: number;
headphone: number;
blasterEnergy: number;
vGatePower: number;
vGateNotes: number;
vGatePeak: number;
vGateTsumami: number;
vGateTricky: number;
vGateOnehand: number;
vGateHandtrip: number;
vGateOverRadar: number[];
blasterCount: number;
extrackEnergy: number;
appeal_frame: number;
@ -50,5 +60,7 @@ export interface Profile {
stampD_R: number;
mainbg: number;
use_pro_team: boolean;
boothFrame: number[];
}

View File

@ -1,7 +1,7 @@
const fs = require('fs');
const path = require('path');
import fs from 'fs';
import path from 'path';
export function unpackS3P(directory, filePath, names) {
export function unpackS3P(directory: string, filePath: string, names: { [x: string]: string | number; }) {
const stream = fs.readFileSync(filePath);
if (stream.slice(0, 4).toString() !== 'S3P0') {
throw new Error('Invalid S3P file');
@ -33,7 +33,6 @@ export function unpackS3P(directory, filePath, names) {
offset += 4;
const headerExtra = stream.slice(offset, offset + hlen - 8);
offset += hlen - 8;
// const [wmaFileLength, , , , , , ,] = new Uint32Array(headerExtra.buffer);
const data = stream.slice(offset, offset + length - hlen);
offset += length - hlen;
@ -44,17 +43,17 @@ export function unpackS3P(directory, filePath, names) {
}
}
export function packS3P(directory, output, names) {
export function packS3P(directory: string, output: string, names: { [x: string]: string | number; }) {
let paths = fs.readdirSync(directory);
if (names) {
const namesBack = {};
for (const key in names) {
namesBack[names[key]] = key;
}
paths = paths.filter((i) => namesBack[i.split('.')[0]]);
paths.sort((a, b) => namesBack[a.split('.')[0]] - namesBack[b.split('.')[0]]);
paths = paths.filter((i: string) => namesBack[i.split('.')[0]]);
paths.sort((a: string, b: string) => namesBack[a.split('.')[0]] - namesBack[b.split('.')[0]]);
} else {
paths.sort((a, b) => parseInt(a.split('.')[0]) - parseInt(b.split('.')[0]));
paths.sort((a: string, b: string) => parseInt(a.split('.')[0]) - parseInt(b.split('.')[0]));
}
let offset = 0;
@ -119,7 +118,7 @@ function usage() {
process.exit(1);
}
function loadNames(filePath) {
function loadNames(filePath: string) {
const base = path.join(path.dirname(filePath), path.basename(filePath, path.extname(filePath)));
const filenames = {};
@ -149,63 +148,63 @@ function loadNames(filePath) {
return filenames;
}
// function main() {
// if (process.argv.length !== 5 && process.argv.length !== 6) {
// usage();
// }
// if (process.argv[2] !== 'pack' && process.argv[2] !== 'unpack') {
// usage();
// }
function main() {
if (process.argv.length !== 5 && process.argv.length !== 6) {
usage();
}
if (process.argv[2] !== 'pack' && process.argv[2] !== 'unpack') {
usage();
}
// const s3p = process.argv[3];
// const directory = process.argv[4];
const s3p = process.argv[3];
const directory = process.argv[4];
// let names = {};
// if (process.argv.length === 6) {
// names = loadNames(process.argv[5]);
// } else {
// names = loadNames(s3p);
// }
let names = {};
if (process.argv.length === 6) {
names = loadNames(process.argv[5]);
} else {
names = loadNames(s3p);
}
// if (!names) {
// console.log('W: Filenames not loaded');
// }
if (!names) {
console.log('W: Filenames not loaded');
}
// if (process.argv[2] === 'pack') {
// if (!fs.existsSync(directory)) {
// console.error(`F: No such file or directory ${directory}`);
// process.exit(1);
// }
if (process.argv[2] === 'pack') {
if (!fs.existsSync(directory)) {
console.error(`F: No such file or directory ${directory}`);
process.exit(1);
}
// const files = fs.readdirSync(directory);
// if (!files.every((i) => /^\d+\.wma$/.test(i) || Object.values(names).includes(i.split('.')[0]))) {
// console.error('F: Files must all be [number].wma');
// process.exit(1);
// }
const files = fs.readdirSync(directory);
if (!files.every((i: string) => /^\d+\.wma$/.test(i) || Object.values(names).includes(i.split('.')[0]))) {
console.error('F: Files must all be [number].wma');
process.exit(1);
}
// const dirname = path.dirname(s3p);
// if (dirname) {
// fs.mkdirSync(dirname, { recursive: true });
// }
const dirname = path.dirname(s3p);
if (dirname) {
fs.mkdirSync(dirname, { recursive: true });
}
// packS3P(directory, s3p, names);
// console.log(`I: ${s3p}`);
// } else {
// if (!fs.existsSync(s3p)) {
// console.error(`F: No such file or directory ${s3p}`);
// process.exit(1);
// }
packS3P(directory, s3p, names);
console.log(`I: ${s3p}`);
} else {
if (!fs.existsSync(s3p)) {
console.error(`F: No such file or directory ${s3p}`);
process.exit(1);
}
// if (fs.existsSync(directory) && !fs.lstatSync(directory).isDirectory()) {
// console.error('F: Output is not a directory');
// process.exit(1);
// }
if (fs.existsSync(directory) && !fs.lstatSync(directory).isDirectory()) {
console.error('F: Output is not a directory');
process.exit(1);
}
// fs.mkdirSync(directory, { recursive: true });
// unpackS3P(directory, s3p, names);
// }
// }
fs.mkdirSync(directory, { recursive: true });
unpackS3P(directory, s3p, names);
}
}
// if (require.main === module) {
// main();
// }
if (require.main === module) {
main();
}

View File

@ -13,7 +13,20 @@ game
headphone(__type="u8") #{headphone}
blaster_energy(__type="u32") #{blasterEnergy}
blaster_count(__type="u32") 0
extrack_energy(__type="u16") #{extrackEnergy}
//- extrack_energy(__type="u16") #{extrackEnergy}
variant_gate
power(__type="s32") #{vGatePower}
element
notes(__type="s32") #{vGateNotes}
peak(__type="s32") #{vGatePeak}
tsumami(__type="s32") #{vGateTsumami}
tricky(__type="s32") #{vGateTricky}
onehand(__type="s32") #{vGateOnehand}
handtrip(__type="s32") #{vGateHandtrip}
if vGateOverRadar.length > 0
over_radar(__type="s32" __count=vGateOverRadar.length) #{vGateOverRadar.join(" ")}
else
over_radar(__type="s32" __count="0")/
hispeed(__type="s32") #{hiSpeed}
lanespeed(__type="u32") #{laneSpeed}
@ -112,10 +125,12 @@ game
ultimate_rate(__type="s32") 0
rank_play_cnt(__type="s32") 1
ultimate_play_cnt(__type="s32") 1
//- additional_info
support_team_id(__type="s32") #{support_team}
if use_pro_team
additional_info
pro_team_id(val= support_team)
else
support_team_id(__type="s32") #{support_team}
if mixes
each mix in mixes
@ -130,4 +145,9 @@ game
jacket_id(__type="s32") #{mix.jacket}
tag_bit(__type="s32") #{mix.tag}
like_flg(__type="u8") 0
//- weekly_music
//- week_id(__type="s32") 0
//- music_id(__type="s32") 1660
//- music_type(__type="s32") 5
//- exscore(__type="u32") 3000
//- rank(__type="s32") 5

View File

@ -1,5 +1,6 @@
import {Counter} from './models/counter';
import { music_db } from '.';
// import { music_db } from '.';
export function IDToCode(id: number) {
const padded = _.padStart(id.toString(), 8);
@ -23,6 +24,7 @@ export function getVersion(info: EamuseInfo) {
if (info.method.startsWith('sv4')) return 4;
if (info.method.startsWith('sv5')) return 5;
if (info.method.startsWith('sv6')) return 6;
if (info.method.startsWith('sv7')) return 7;
return 0;
}
@ -159,4 +161,109 @@ export function send_webhook(data: any) {
});
req.write(contents);
}
}
export function getCurrentWeekOfYear(date = new Date()) {
// Clone the date to avoid modifying the original
const current = date.getTime();
// Set the first day of the year
const startOfYear = new Date(date.getFullYear(), 0, 1).getTime();
// Calculate the day of the year
const dayOfYear = ((current - startOfYear + 1) / 86400000);
// ISO 8601 weeks start on Monday and the first week of the year must contain Jan 4th.
// Adjust the date to the nearest Thursday (ISO 8601 rule).
const adjustedDate = new Date(
date.getFullYear(),
date.getMonth(),
date.getDate() + (4 - (date.getDay() || 7))
);
const startOfISOYear = new Date(adjustedDate.getFullYear(), 0, 1);
const firstWeekDay = startOfISOYear.getDay() || 7;
// Calculate ISO week number
return Math.ceil((adjustedDate.getTime() - startOfISOYear.getTime() + (firstWeekDay - 1) * 86400000) / (7 * 86400000));
}
export function getWeekStartAndEnd(date = new Date()) {
// Clone the input date to avoid modifying the original
const current = new Date(date.getTime());
// Get the day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday)
const dayOfWeek = current.getDay();
// Adjust to the start of the week (Monday)
const diffToMonday = (dayOfWeek === 0 ? -6 : 1) - dayOfWeek; // Monday = 1, Sunday = 0
const startOfWeek = new Date(current.setDate(current.getDate() + diffToMonday));
startOfWeek.setHours(0, 0, 0, 0); // Set time to midnight
// Clone the startOfWeek and add 6 days to get the end of the week
const endOfWeek = new Date(startOfWeek.getTime());
endOfWeek.setDate(endOfWeek.getDate() + 6);
endOfWeek.setHours(23, 59, 59, 999); // Set time to end of day
return {
startOfWeek: startOfWeek.getTime(), // Timestamp for the start of the week
endOfWeek: endOfWeek.getTime(), // Timestamp for the end of the week
};
}
export class SeededRandom {
seed: number;
constructor(seed) {
this.seed = seed % 2147483647; // A prime number
if (this.seed <= 0) this.seed += 2147483646; // Avoid zero seed
}
next() {
this.seed = (this.seed * 16807) % 2147483647; // LCG formula
return this.seed;
}
nextFloat() {
return (this.next() - 1) / 2147483646; // Convert to [0, 1)
}
}
export const TRANSLATION_TABLE = {
"龕": "€",
"釁": "🍄",
"驩": "Ø",
"曦": "à",
"齷": "é",
"骭": "ü",
"齶": "♡",
"彜": "ū",
"罇": "ê",
"雋": "Ǜ",
"鬻": "♃",
"鬥": "Ã",
"鬆": "Ý",
"曩": "è",
"驫": "ā",
"齲": "♥",
"騫": "á",
"趁": "Ǣ",
"鬮": "¡",
"盥": "⚙︎",
"隍": "︎Ü",
"頽": "ä",
"餮": "Ƶ",
"黻": "*",
"蔕": "ũ",
"闃": "Ā",
"饌": "²",
"煢": "ø",
"鑷": "ゔ",
"=墸Σ": "=͟͟͞ Σ",
"鹹": "Ĥ",
"瀑i": "Ài",
"疉": "Ö",
"鑒": "₩",
"Ryu??": "Ryu☆",
}

184
sdvx@asphyxia/utils/zip.ts Normal file
View File

@ -0,0 +1,184 @@
import fs from 'fs';
import path from 'path';
function crc32(buffer: Buffer): number {
let crc = 0xffffffff;
for (let i = 0; i < buffer.length; i++) {
crc ^= buffer[i];
for (let j = 0; j < 8; j++) {
const mask = -(crc & 1);
crc = (crc >>> 1) ^ (0xedb88320 & mask);
}
}
return (crc ^ 0xffffffff) >>> 0;
}
function toDosTimeDate(date: Date): { time: number; date: number } {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hours = date.getHours();
const minutes = date.getMinutes();
const seconds = Math.floor(date.getSeconds() / 2);
const dosTime = (hours << 11) | (minutes << 5) | seconds;
const dosDate = ((Math.max(year, 1980) - 1980) << 9) | (month << 5) | day;
return { time: dosTime & 0xffff, date: dosDate & 0xffff };
}
async function listFilesRecursive(rootDir: string): Promise<string[]> {
const results: string[] = [];
async function walk(currentDir: string) {
const entries = await fs.promises.readdir(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
await walk(fullPath);
} else if (entry.isFile()) {
results.push(fullPath);
}
}
}
await walk(rootDir);
return results;
}
function normalizeZipPath(p: string): string {
return p.replace(/\\/g, '/');
}
export async function zipFolderToFile(params: {
sourceDir: string;
outZipPath: string;
rootInZip?: string;
}): Promise<void> {
const sourceDir = path.resolve(params.sourceDir);
const outZipPath = path.resolve(params.outZipPath);
const rootInZip = params.rootInZip ? normalizeZipPath(params.rootInZip).replace(/^\/+|\/+$/g, '') : '';
await fs.promises.mkdir(path.dirname(outZipPath), { recursive: true });
const files = await listFilesRecursive(sourceDir);
const out = fs.createWriteStream(outZipPath);
let offset = 0;
type CentralEntry = {
fileName: string;
crc: number;
compressedSize: number;
uncompressedSize: number;
modTime: number;
modDate: number;
localHeaderOffset: number;
};
const central: CentralEntry[] = [];
const writeBuffer = async (buf: Buffer) => {
if (buf.length === 0) return;
await new Promise<void>((resolve, reject) => {
out.write(buf, (err) => (err ? reject(err) : resolve()));
});
offset += buf.length;
};
for (const filePath of files) {
const stat = await fs.promises.stat(filePath);
const data = await fs.promises.readFile(filePath);
const rel = normalizeZipPath(path.relative(sourceDir, filePath));
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) {
continue;
}
const fileName = rootInZip ? `${rootInZip}/${rel}` : rel;
const fileNameBytes = Buffer.from(fileName, 'utf8');
const crc = crc32(data);
const uncompressedSize = data.length;
const compressedSize = data.length;
const { time: modTime, date: modDate } = toDosTimeDate(stat.mtime);
const localHeaderOffset = offset;
const localHeader = Buffer.alloc(30);
localHeader.writeUInt32LE(0x04034b50, 0); // Local file header signature
localHeader.writeUInt16LE(20, 4); // Version needed to extract
localHeader.writeUInt16LE(0, 6); // General purpose bit flag
localHeader.writeUInt16LE(0, 8); // Compression method (0 = store)
localHeader.writeUInt16LE(modTime, 10);
localHeader.writeUInt16LE(modDate, 12);
localHeader.writeUInt32LE(crc, 14);
localHeader.writeUInt32LE(compressedSize, 18);
localHeader.writeUInt32LE(uncompressedSize, 22);
localHeader.writeUInt16LE(fileNameBytes.length, 26);
localHeader.writeUInt16LE(0, 28); // Extra field length
await writeBuffer(localHeader);
await writeBuffer(fileNameBytes);
await writeBuffer(data);
central.push({
fileName,
crc,
compressedSize,
uncompressedSize,
modTime,
modDate,
localHeaderOffset,
});
}
const centralDirOffset = offset;
for (const entry of central) {
const fileNameBytes = Buffer.from(entry.fileName, 'utf8');
const header = Buffer.alloc(46);
header.writeUInt32LE(0x02014b50, 0); // Central directory file header signature
header.writeUInt16LE(20, 4); // Version made by
header.writeUInt16LE(20, 6); // Version needed to extract
header.writeUInt16LE(0, 8); // General purpose bit flag
header.writeUInt16LE(0, 10); // Compression method
header.writeUInt16LE(entry.modTime, 12);
header.writeUInt16LE(entry.modDate, 14);
header.writeUInt32LE(entry.crc, 16);
header.writeUInt32LE(entry.compressedSize, 20);
header.writeUInt32LE(entry.uncompressedSize, 24);
header.writeUInt16LE(fileNameBytes.length, 28);
header.writeUInt16LE(0, 30); // Extra field length
header.writeUInt16LE(0, 32); // File comment length
header.writeUInt16LE(0, 34); // Disk number start
header.writeUInt16LE(0, 36); // Internal file attributes
header.writeUInt32LE(0, 38); // External file attributes
header.writeUInt32LE(entry.localHeaderOffset, 42);
await writeBuffer(header);
await writeBuffer(fileNameBytes);
}
const centralDirSize = offset - centralDirOffset;
const eocd = Buffer.alloc(22);
eocd.writeUInt32LE(0x06054b50, 0); // End of central directory signature
eocd.writeUInt16LE(0, 4); // Number of this disk
eocd.writeUInt16LE(0, 6); // Disk where central directory starts
eocd.writeUInt16LE(central.length, 8); // Number of central directory records on this disk
eocd.writeUInt16LE(central.length, 10); // Total number of central directory records
eocd.writeUInt32LE(centralDirSize, 12);
eocd.writeUInt32LE(centralDirOffset, 16);
eocd.writeUInt16LE(0, 20); // ZIP file comment length
await writeBuffer(eocd);
await new Promise<void>((resolve, reject) => {
out.end(() => resolve());
out.on('error', reject);
});
}

Some files were not shown because too many files have changed in this diff Show More