mirror of
https://github.com/asphyxia-core/plugins.git
synced 2026-03-21 17:34:46 -05:00
commit
910c134f12
|
|
@ -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
4541
sdvx@asphyxia/data/exg_gene.ts
Normal file
4541
sdvx@asphyxia/data/exg_gene.ts
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -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)),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
})),
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,4 +10,7 @@ export interface MusicRecord {
|
|||
buttonRate: number;
|
||||
longRate: number;
|
||||
volRate: number;
|
||||
volforce: number;
|
||||
|
||||
judge: number[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
184
sdvx@asphyxia/utils/zip.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user