Merge branch 'stable' of https://github.com/cracrayol/plugins into stable

This commit is contained in:
cracrayol
2021-06-19 12:17:48 +02:00
51 changed files with 4113 additions and 684 deletions

18
ddr@asphyxia/README.md Normal file
View File

@@ -0,0 +1,18 @@
# Dance Dance Revolution
![Version](https://img.shields.io/badge/Version-v1.0.0-brightgreen?style=for-the-badge)
---
Supported version
- Dance Dance Revolution A20
- Dance Dance Revolution A
---
Changelogs
**v1.0.0**
- Initial release

View File

@@ -0,0 +1,18 @@
export const eventLog: EPR = (info, data, send) => {
return send.object({
gamesession: K.ITEM("s64", BigInt(1)),
logsendflg: K.ITEM("s32", 0),
logerrlevel: K.ITEM("s32", 0),
evtidnosendflg: K.ITEM("s32", 0)
});
};
export const convcardnumber: EPR = (info, data, send) => {
return send.object({
result: K.ITEM("s32", 0),
data: {
card_number: K.ITEM("str", $(data).str("data.card_id").split("|")[0])
}
});
};

View File

@@ -0,0 +1,275 @@
import { CommonOffset, LastOffset, OptionOffset, Profile } from "../models/profile";
import { formatCode } from "../utils";
import { Score } from "../models/score";
import { Ghost } from "../models/ghost";
enum GameStyle {
SINGLE,
DOUBLE,
VERSUS
}
export const usergamedata: EPR = async (info, data, send) => {
const mode = $(data).str("data.mode");
const refId = $(data).str("data.refid");
switch (mode) {
case "userload":
return send.object(await userload(refId));
case "usernew":
return send.object(await usernew(refId, data));
case "usersave":
return send.object(await usersave(refId, data));
case "rivalload":
return send.object(await rivalload(refId, data));
case "ghostload":
return send.object(await ghostload(refId, data));
case "inheritance":
return send.object(inheritance(refId));
default:
return send.deny();
}
};
const userload = async (refId: string) => {
let resObj = {
result: K.ITEM("s32", 0),
is_new: K.ITEM("bool", false),
music: [],
eventdata: []
};
if (!refId.startsWith("X000")) {
const profile = await DB.FindOne<Profile>(refId, { collection: "profile" });
if (!profile) resObj.is_new = K.ITEM("bool", true);
const scores = await DB.Find<Score>(refId, { collection: "score" });
for (const score of scores) {
const note = [];
for (let i = 0; i < 9; i++) {
if (score.difficulty !== i) {
note.push({
count: K.ITEM("u16", 0),
rank: K.ITEM("u8", 0),
clearkind: K.ITEM("u8", 0),
score: K.ITEM("s32", 0),
ghostid: K.ITEM("s32", 0)
});
} else {
note.push({
count: K.ITEM("u16", 1),
rank: K.ITEM("u8", score.rank),
clearkind: K.ITEM("u8", score.clearKind),
score: K.ITEM("s32", score.score),
ghostid: K.ITEM("s32", score.songId)
});
}
}
resObj.music.push({
mcode: K.ITEM("u32", score.songId),
note
});
}
resObj["grade"] = {
single_grade: K.ITEM("u32", profile.singleGrade || 0),
dougle_grade: K.ITEM("u32", profile.doubleGrade || 0)
};
}
return resObj;
};
const usernew = async (refId: string, data: any) => {
const shopArea = $(data).str("data.shoparea", "");
let profile = await DB.FindOne<Profile>(refId, { collection: "profile" });
if (!profile) {
profile = (await DB.Upsert<Profile>(refId, { collection: "profile" }, {
collection: "profile",
ddrCode: _.random(1, 99999999),
shopArea
})).docs[0];
}
return {
result: K.ITEM("s32", 0),
seq: K.ITEM("str", formatCode(profile.ddrCode)),
code: K.ITEM("s32", profile.ddrCode),
shoparea: K.ITEM("str", profile.shopArea),
};
};
const usersave = async (refId: string, serverData: any) => {
const profile = await DB.FindOne<Profile>(refId, { collection: "profile" });
if (profile) {
const data = $(serverData).element("data");
const notes = data.elements("note");
const events = data.elements("event");
const common = profile.usergamedata.COMMON.strdata.split(",");
const option = profile.usergamedata.OPTION.strdata.split(",");
const last = profile.usergamedata.LAST.strdata.split(",");
if (data.bool("isgameover")) {
const style = data.number("playstyle");
if (style === GameStyle.DOUBLE) {
common[CommonOffset.DOUBLE_PLAYS] = (parseInt(common[CommonOffset.DOUBLE_PLAYS]) + 1) + "";
} else {
common[CommonOffset.SINGLE_PLAYS] = (parseInt(common[CommonOffset.SINGLE_PLAYS]) + 1) + "";
}
common[CommonOffset.TOTAL_PLAYS] = (+common[CommonOffset.DOUBLE_PLAYS]) + (+common[CommonOffset.SINGLE_PLAYS]) + "";
const workoutEnabled = !!+common[CommonOffset.WEIGHT_DISPLAY];
const workoutWeight = +common[CommonOffset.WEIGHT];
if (workoutEnabled && workoutWeight > 0) {
let total = 0;
for (const note of notes) {
total = total + note.number("calorie", 0);
}
last[LastOffset.CALORIES] = total + "";
}
for (const event of events) {
const eventId = event.number("eventid", 0);
const eventType = event.number("eventtype", 0);
if (eventId === 0 || eventType === 0) continue;
const eventCompleted = event.number("comptime") !== 0;
const eventProgress = event.number("savedata");
if (!profile.events) profile.events = {};
profile.events[eventId] = {
completed: eventCompleted,
progress: eventProgress
};
}
const gradeNode = data.element("grade");
if (gradeNode) {
const single = gradeNode.number("single_grade", 0);
const double = gradeNode.number("double_grade", 0);
profile.singleGrade = single;
profile.doubleGrade = double;
}
}
let scoreData: KDataReader | null;
let stageNum = 0;
for (const note of notes) {
if (note.number("stagenum") > stageNum) {
scoreData = note;
stageNum = note.number("stagenum");
}
}
if (scoreData) {
const songId = scoreData.number("mcode");
const difficulty = scoreData.number("notetype");
const rank = scoreData.number("rank");
const clearKind = scoreData.number("clearkind");
const score = scoreData.number("score");
const maxCombo = scoreData.number("maxcombo");
const ghostSize = scoreData.number("ghostsize");
const ghost = scoreData.str("ghost");
option[OptionOffset.SPEED] = scoreData.number("opt_speed").toString(16);
option[OptionOffset.BOOST] = scoreData.number("opt_boost").toString(16);
option[OptionOffset.APPEARANCE] = scoreData.number("opt_appearance").toString(16);
option[OptionOffset.TURN] = scoreData.number("opt_turn").toString(16);
option[OptionOffset.STEP_ZONE] = scoreData.number("opt_dark").toString(16);
option[OptionOffset.SCROLL] = scoreData.number("opt_scroll").toString(16);
option[OptionOffset.ARROW_COLOR] = scoreData.number("opt_arrowcolor").toString(16);
option[OptionOffset.CUT] = scoreData.number("opt_cut").toString(16);
option[OptionOffset.FREEZE] = scoreData.number("opt_freeze").toString(16);
option[OptionOffset.JUMP] = scoreData.number("opt_jump").toString(16);
option[OptionOffset.ARROW_SKIN] = scoreData.number("opt_arrowshape").toString(16);
option[OptionOffset.FILTER] = scoreData.number("opt_filter").toString(16);
option[OptionOffset.GUIDELINE] = scoreData.number("opt_guideline").toString(16);
option[OptionOffset.GAUGE] = scoreData.number("opt_gauge").toString(16);
option[OptionOffset.COMBO_POSITION] = scoreData.number("opt_judgepriority").toString(16);
option[OptionOffset.FAST_SLOW] = scoreData.number("opt_timing").toString(16);
await DB.Upsert<Score>(refId, {
collection: "score",
songId,
difficulty
}, {
$set: {
rank,
clearKind,
score,
maxCombo
}
});
await DB.Upsert<Ghost>(refId, {
collection: "ghost",
songId,
difficulty
}, {
$set: {
ghostSize,
ghost
}
});
}
await DB.Update<Profile>(refId, { collection: "profile" }, {
$set: {
"usergamedata.COMMON.strdata": common.join(","),
"usergamedata.OPTION.strdata": option.join(","),
"usergamedata.LAST.strdata": last.join(","),
}
});
}
return {
result: K.ITEM("s32", 0)
};
};
const rivalload = (refId: string, data: any) => {
const loadFlag = $(data).number("data.loadflag");
const record = [];
return {
result: K.ITEM("s32", 0),
data: {
recordtype: K.ITEM("s32", loadFlag),
record
}
};
};
const ghostload = (refId: string, data: any) => {
const ghostdata = {};
return {
result: K.ITEM("s32", 0),
ghostdata
};
};
const inheritance = (refId: string) => {
return {
result: K.ITEM("s32", 0),
InheritanceStatus: K.ITEM("s32", 1)
};
};

View File

@@ -0,0 +1,45 @@
import { Profile } from "../models/profile";
export const usergamedata_recv: EPR = async (info, data, send) => {
const refId = $(data).str("data.refid");
const profile = await DB.FindOne<Profile>(refId, { collection: "profile" });
let recordNum = 0;
const record = [];
const d = [];
const types = $(data).str("data.recv_csv").split(",").filter((_, i) => (i % 2 === 0));
for (const type of types) {
let strdata = "<NODATA>";
let bindata = "<NODATA>";
if (profile) {
strdata = profile.usergamedata[type]["strdata"];
bindata = profile.usergamedata[type]["bindata"];
if (type === "OPTION") {
const split = strdata.split(",");
split[0] = U.GetConfig("save_option") ? "1" : "0";
strdata = split.join(",");
}
}
d.push({
...K.ITEM("str", !profile ? strdata : Buffer.from(strdata).toString("base64")),
...profile && { bin1: K.ITEM("str", Buffer.from(bindata).toString("base64")) }
});
recordNum++;
}
record.push({ d });
return send.object({
result: K.ITEM("s32", 0),
player: {
record,
record_num: K.ITEM("u32", recordNum)
}
});
};

View File

@@ -0,0 +1,31 @@
import { Profile } from "../models/profile";
export const usergamedata_send: EPR = async (info, data, send) => {
const refId = $(data).str("data.refid");
const profile = await DB.FindOne<Profile>(refId, { collection: "profile" });
if (!profile) return send.deny();
for (const record of $(data).elements("data.record.d")) {
const decodeStr = Buffer.from(record.str("", ""), "base64").toString("ascii");
const decodeBin = Buffer.from(record.str("bin1", ""), "base64").toString("ascii");
const strdata = decodeStr.split(",");
const type = Buffer.from(strdata[1]).toString("utf-8");
if (!profile.usergamedata) profile.usergamedata = {};
if (!profile.usergamedata[type]) profile.usergamedata[type] = {};
profile.usergamedata[type] = {
strdata: strdata.slice(2, -1).join(","),
bindata: decodeBin
};
}
try {
await DB.Update<Profile>(refId, { collection: "profile" }, profile);
return send.object({ result: K.ITEM("s32", 0) });
} catch {
return send.deny();
}
};

135
ddr@asphyxia/index.ts Normal file
View File

@@ -0,0 +1,135 @@
import { convcardnumber, eventLog } from "./handlers/common";
import { usergamedata } from "./handlers/usergamedata";
import { usergamedata_recv } from "./handlers/usergamedata_recv";
import { usergamedata_send } from "./handlers/usergamedata_send";
import { CommonOffset, OptionOffset, Profile } from "./models/profile";
export function register() {
R.GameCode("MDX");
R.Config("save_option", {
name: "Save option",
desc: "Gets the previously set options as they are.",
default: true,
type: "boolean"
});
R.Route("playerdata.usergamedata_advanced", usergamedata);
R.Route("playerdata.usergamedata_recv", usergamedata_recv);
R.Route("playerdata.usergamedata_send", usergamedata_send);
R.Route("system.convcardnumber", convcardnumber);
R.Route("eventlog.write", eventLog);
R.WebUIEvent("updateName", async ({ refid, name }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.COMMON.strdata.split(",");
strdata[CommonOffset.NAME] = name;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.COMMON.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateWeight", async ({ refid, weight }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.COMMON.strdata.split(",");
strdata[CommonOffset.WEIGHT] = weight;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.COMMON.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateDisplayCalories", async ({ refid, selected }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.COMMON.strdata.split(",");
strdata[CommonOffset.WEIGHT_DISPLAY] = selected;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.COMMON.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateArrowSkin", async ({ refid, selected }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.OPTION.strdata.split(",");
strdata[OptionOffset.ARROW_SKIN] = selected;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.OPTION.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateGuideline", async ({ refid, selected }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.OPTION.strdata.split(",");
strdata[OptionOffset.GUIDELINE] = selected;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.OPTION.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateFilter", async ({ refid, selected }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.OPTION.strdata.split(",");
strdata[OptionOffset.FILTER] = selected;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.OPTION.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateJudgmentPriority", async ({ refid, selected }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.OPTION.strdata.split(",");
strdata[OptionOffset.COMBO_POSITION] = selected;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.OPTION.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateDisplayTiming", async ({ refid, selected }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.OPTION.strdata.split(",");
strdata[OptionOffset.FAST_SLOW] = selected;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.OPTION.strdata": strdata.join(",")
}
});
}
});
}

View File

@@ -0,0 +1,8 @@
export interface Ghost {
collection: "ghost";
songId: number;
difficulty: number;
ghostSize: number;
ghost: string;
}

View File

@@ -0,0 +1,77 @@
export enum CommonOffset {
AREA = 1,
SEQ_HEX = 1,
WEIGHT_DISPLAY = 3,
CHARACTER,
EXTRA_CHARGE,
TOTAL_PLAYS = 9,
SINGLE_PLAYS = 11,
DOUBLE_PLAYS,
WEIGHT = 17,
NAME = 25,
SEQ
}
export enum OptionOffset {
SPEED = 1,
BOOST,
APPEARANCE,
TURN,
STEP_ZONE,
SCROLL,
ARROW_COLOR,
CUT,
FREEZE,
JUMP,
ARROW_SKIN,
FILTER,
GUIDELINE,
GAUGE,
COMBO_POSITION,
FAST_SLOW
}
export enum LastOffset {
SONG = 3,
CALORIES = 10
}
export enum RivalOffset {
RIVAL_1_ACTIVE = 1,
RIVAL_2_ACTIVE,
RIVAL_3_ACTIVE,
RIVAL_1_DDRCODE = 9,
RIVAL_2_DDRCODE,
RIVAL_3_DDRCODE,
}
export interface Profile {
collection: "profile";
ddrCode: number;
shopArea: string;
singleGrade?: number;
doubleGrade?: number;
events?: {};
usergamedata?: {
COMMON?: {
strdata?: string;
bindata?: string;
};
OPTION?: {
strdata?: string;
bindata?: string;
};
LAST?: {
strdata?: string;
bindata?: string;
};
RIVAL?: {
strdata?: string;
bindata?: string;
};
};
}

View File

@@ -0,0 +1,49 @@
export enum Difficulty {
SINGLE_BEGINNER,
SINGLE_BASIC,
SINGLE_DIFFICULT,
SINGLE_EXPERT,
SINGLE_CHALLENGE,
DOUBLE_BASIC,
DOUBLE_DIFFICULT,
DOUBLE_EXPERT,
DOUBLE_CHALLENGE
}
export enum Rank {
AAA,
AA_PLUS,
AA,
AA_MINUS,
A_PLUS,
A,
A_MINUS,
B_PLUS,
B,
B_MINUS,
C_PLUS,
C,
C_MINUS,
D_PLUS,
D,
E
}
export enum ClearKind {
NONE = 6,
GOOD_COMBO,
GREAT_COMBO,
PERPECT_COMBO,
MARVELOUS_COMBO
}
export interface Score {
collection: "score";
songId: number;
difficulty: Difficulty;
rank: Rank;
clearKind: ClearKind;
score: number;
maxCombo: number;
}

13
ddr@asphyxia/utils.ts Normal file
View File

@@ -0,0 +1,13 @@
export function getVersion(info: EamuseInfo) {
const dateCode = parseInt(info.model.split(":")[4]);
if (dateCode >= 2019022600 && dateCode <= 2020020300) return 10;
return 0;
}
export function formatCode(ddrCode: number) {
const pad = (ddrCode + "").padStart(8, "0");
return pad.replace(/^([0-9]{4})([0-9]{4})$/, "$1-$2");
}

View File

@@ -0,0 +1,49 @@
$('#change-name').on('click', () => {
const name = $('#dancer_name').val().toUpperCase();
emit('updateName', { refid, name }).then(() => location.reload());
});
$('#change-weight').on('click', () => {
const weight1 = $('#weight_1').val();
const weight2 = $('#weight_2').val();
const weight = weight1 + '.' + weight2;
emit('updateWeight', { refid, weight }).then(() => location.reload());
});
$('#change-display-calories').on('click', () => {
const selected = $('#display_calories option:selected').val();
emit('updateDisplayCalories', { refid, selected }).then(() => location.reload());
});
$('#change-arrow-skin').on('click', () => {
const selected = $('#arrow_skin option:selected').val();
emit('updateArrowSkin', { refid, selected }).then(() => location.reload());
});
$('#change-guideline').on('click', () => {
const selected = $('#guideline option:selected').val();
emit('updateGuideline', { refid, selected }).then(() => location.reload());
});
$('#change-filter').on('click', () => {
const selected = $('#filter option:selected').val();
emit('updateFilter', { refid, selected }).then(() => location.reload());
});
$('#change-judgment-priority').on('click', () => {
const selected = $('#judgment_priority option:selected').val();
emit('updateJudgmentPriority', { refid, selected }).then(() => location.reload());
});
$('#change-display-timing').on('click', () => {
const selected = $('#display_timing option:selected').val();
emit('updateDisplayTiming', { refid, selected }).then(() => location.reload());
});

View File

@@ -0,0 +1,147 @@
//DATA//
profile: DB.FindOne(refid, { collection: "profile" })
-
const onOff = [ "Off", "On" ];
const characters = [ "All Character Random", "Man Random", "Female Random", "Yuni", "Rage", "Afro", "Jenny", "Emi", "Baby-Lon", "Gus", "Ruby", "Alice", "Julio", "Bonnie", "Zero", "Rinon" ];
const arrowSkins = [ "Normal", "X", "Classic", "Cyber", "Medium", "Small", "Dot" ];
const guidelines = [ "Off", "Border", "Center" ];
const filters = [ "Off", "Dark", "Darker", "Darkest" ];
const judgmentPrioritys = [ "Judgment priority", "Arrow priority" ];
if (profile.usergamedata)
-
const common = profile.usergamedata.COMMON.strdata.split(",");
const option = profile.usergamedata.OPTION.strdata.split(",");
const name = common[25];
const weight = common[17];
const displayCalories = parseInt(common[3]);
const character = parseInt(common[4]);
const arrowSkin = parseInt(option[11]);
const guideline = parseInt(option[13]);
const filter = parseInt(option[12]);
const judgmentPriority = parseInt(option[15]);
const displayTiming = parseInt(option[16]);
div
.card
.card-header
p.card-header-title
span.icon
i.mdi.mdi-cog
| Profile Settings
.card-content
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Dancer Name
.field-body
p.control
input.input(type="text", id="dancer_name", pattern="[A-Z]{8}", maxlength=8, value=name)
p.control
a.button.is-primary#change-name Change
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Workout Weight
.field-body
p.control
input.input(type="number", id="weight_1", value=weight.split(".")[0])
p.control
input.input(type="number", id="weight_2", value=weight.split(".")[1])
p.control
a.button.is-primary#change-weight Change
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Workout Display Calories
.field-body
p.control
.select
select#display_calories
if (displayCalories === 1)
option(value=0) Off
option(value=1, selected) On
else
option(value=0, selected) Off
option(value=1) On
p.control
a.button.is-primary#change-display-calories Submit
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Arrow Skin
.field-body
p.control
.select
select#arrow_skin
each v, i in arrowSkins
if (arrowSkin === i)
option(value=i, selected) #{v}
else
option(value=i) #{v}
p.control
a.button.is-primary#change-arrow-skin Submit
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Guideline
.field-body
p.control
.select
select#guideline
each v, i in guidelines
if (guideline === i)
option(value=i, selected) #{v}
else
option(value=i) #{v}
p.control
a.button.is-primary#change-guideline Submit
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Filter concentration
.field-body
p.control
.select
select#filter
each v, i in filters
if (filter === i)
option(value=i, selected) #{v}
else
option(value=i) #{v}
p.control
a.button.is-primary#change-filter Submit
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Judgment display priority
.field-body
p.control
.select
select#judgment_priority
each v, i in judgmentPrioritys
if (judgmentPriority === i)
option(value=i, selected) #{v}
else
option(value=i) #{v}
p.control
a.button.is-primary#change-judgment-priority Submit
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Display Timing judgment
.field-body
p.control
.select
select#display_timing
each v, i in ["Off", "On"]
if (displayTiming === i)
option(value=i, selected) #{v}
else
option(value=i) #{v}
p.control
a.button.is-primary#change-display-timing Submit
script(src="static/js/profile_settings.js")

View File

@@ -1,15 +1,45 @@
GITADORA Plugin for Asphyxia-Core
=================================
This plugin is converted from public-exported Asphyxia's Routes.
![Version: v1.1.1](https://img.shields.io/badge/version-v1.1.1-blue)
This plugin is based on converted from public-exported Asphyxia's Routes.
Supported Versions
==================
- Matixx
- Exchain
- NEX+AGE
When Plugin Doesn't work correctly / Startup Error on Plugin
------------------------------------------------------------
The folder structure between v1.0 and v1.1 is quite different. Do not overwrite plugin folder.
<br>If encounter error, Please try these step:
1. Remove `gitadora@asphyxia` folder.
2. C-C and C-V the newest version of `gitadora@asphyxia`
3. (Custom MDB Users) Reupload MDB or move `data/custom_mdb.xml` to `data/mdb/custom.xml`
Known Issues
============
* Information dialog keep showing as plugin doesn't store item data currently.
* Special Premium Encore on Nextage
- Bandage solution is implemented. Try it.
Release Notes
=============
v1.0.0 (Current)
v1.1.1 (Current)
----------------
* fix: Error when create new profile on exchain.
* fix: last song doesn't work correctly.
* misc: Add logger for tracking problem.
v1.1.0
------
* NEX+AGE Support (Not full support.)
* Restructure bit for maintaining.
v1.0.0
------
* Initial release for public

View File

@@ -1,5 +0,0 @@
mdb_ex.xml
mdb_mt.xml
mdb_ex.b64
mdb_mt.b64
custom_mdb.xml

View File

@@ -1,58 +0,0 @@
import { CommonMusicData, readJSONOrXML, readXML } from './helper';
export async function processData() {
const { music } = await readJSONOrXML('data/mdb_ex.json', 'data/mdb_ex.xml', processRawData)
return {
music,
};
}
export async function processRawData(path: string): Promise<CommonMusicData> {
const data = await readXML(path)
const mdb = $(data).elements("mdb.mdb_data");
const music: any[] = [];
for (const m of mdb) {
const d = m.numbers("xg_diff_list");
const contain = m.numbers("contain_stat");
const gf = contain[0];
const dm = contain[1];
if (gf == 0 && dm == 0) {
continue;
}
let type = gf;
if (gf == 0) {
type = dm;
}
music.push({
id: K.ITEM('s32', m.number("music_id")),
cont_gf: K.ITEM('bool', gf == 0 ? 0 : 1),
cont_dm: K.ITEM('bool', dm == 0 ? 0 : 1),
is_secret: K.ITEM('bool', 0),
is_hot: K.ITEM('bool', type == 2 ? 0 : 1),
data_ver: K.ITEM('s32', m.number("data_ver")),
diff: K.ARRAY('u16', [
d[0],
d[1],
d[2],
d[3],
d[4],
d[10],
d[11],
d[12],
d[13],
d[14],
d[5],
d[6],
d[7],
d[8],
d[9],
]),
});
}
return {
music,
};
}

View File

@@ -1,58 +0,0 @@
import { CommonMusicData, readJSONOrXML, readXML } from './helper';
export async function processData() {
const { music } = await readJSONOrXML('data/mdb_mt.json', 'data/mdb_mt.xml', processRawData)
return {
music,
};
}
export async function processRawData(path: string): Promise<CommonMusicData> {
const data = await readXML(path)
const mdb = $(data).elements("mdb.mdb_data");
const music: any[] = [];
for (const m of mdb) {
const d = m.numbers("xg_diff_list");
const contain = m.numbers("contain_stat");
const gf = contain[0];
const dm = contain[1];
if (gf == 0 && dm == 0) {
continue;
}
let type = gf;
if (gf == 0) {
type = dm;
}
music.push({
id: K.ITEM('s32', m.number("music_id")),
cont_gf: K.ITEM('bool', gf == 0 ? 0 : 1),
cont_dm: K.ITEM('bool', dm == 0 ? 0 : 1),
is_secret: K.ITEM('bool', 0),
is_hot: K.ITEM('bool', type == 2 ? 0 : 1),
data_ver: K.ITEM('s32', m.number("data_ver")),
diff: K.ARRAY('u16', [
d[0],
d[1],
d[2],
d[3],
d[4],
d[10],
d[11],
d[12],
d[13],
d[14],
d[5],
d[6],
d[7],
d[8],
d[9],
]),
});
}
return {
music,
};
}

View File

@@ -0,0 +1,72 @@
import { getVersion } from "../utils";
interface EncoreStageData {
level: number
musics: number[]
unlock_challenge?: number[]
}
export function getEncoreStageData(info: EamuseInfo): EncoreStageData {
const fallback = { level: 10, musics: [0] }
const level: number = U.GetConfig("encore_version")
const ntDummyEncore = U.GetConfig("nextage_dummy_encore")
switch (getVersion(info)) {
case 'nextage':
return {
level,
musics: !ntDummyEncore ? [
2587, // 悪魔のハニープリン
2531, // The ULTIMATES -reminiscence-
2612, // ECLIPSE 2
2622, // Slip Into My Royal Blood
2686, // CYCLONICxSTORM
// FIXME: Fix special encore.
305, 602, 703, 802, 902, 1003, 1201, 1400, 1712, 1916, 2289, 2631, // DD13 and encores.
1704, 1811, 2121, 2201, 2624, // Soranaki and encores.
1907, 2020, 2282, 2341, 2666 // Stargazer and encores.
] : [
2622, 305, 1704, 1907, 2686 // Dummy.
]
}
case 'exchain':
return {
level,
musics: [
2246, // 箱庭の世界
2498, // Cinnamon
2500, // キヤロラ衛星の軌跡
2529, // グリーンリーフ症候群
2548, // Let's Dance
2587, // 悪魔のハニープリン
5020, // Timepiece phase II (CLASSIC)
5033, // MODEL FT2 Miracle Version (CLASSIC)
2586, // 美麗的夏日風
5060, // EXCELSIOR DIVE (CLASSIC)
2530, // The ULTIMATES -CHRONICLE-
2581, // 幸せの代償
5046 // Rock to Infinity (CLASSIC)
]
}
case 'matixx':
return {
level,
musics: [
2432, // Durian
2445, // ヤオヨロズランズ
2456, // Fate of the Furious
2441, // PIRATES BANQUET
2444, // Aion
2381, // Duella Lyrica
2471, // triangulum
2476, // MODEL FT4
2486, // 煉獄事変
2496, // CAPTURING XANADU
2497, // Physical Decay
2499, // Cinnamon
2498 // けもののおうじゃ★めうめう
]
}
default:
return fallback
}
}

View File

@@ -1,36 +0,0 @@
export interface CommonMusicDataField {
id: KITEM<"s32">;
cont_gf: KITEM<"bool">;
cont_dm: KITEM<"bool">;
is_secret: KITEM<"bool">;
is_hot: KITEM<"bool">;
data_ver: KITEM<"s32">;
diff: KARRAY<"u16">;
}
export interface CommonMusicData {
music: CommonMusicDataField[]
}
export async function readXML(path: string) {
const xml = await IO.ReadFile(path, 'utf-8');
const json = U.parseXML(xml, false)
return json
}
export async function readJSON(path: string) {
const str = await IO.ReadFile(path, 'utf-8');
const json = JSON.parse(str)
return json
}
export async function readJSONOrXML(jsonPath: string, xmlPath: string, processHandler: (path: string) => Promise<CommonMusicData>): Promise<CommonMusicData> {
if (!IO.Exists(jsonPath)) {
const data = await processHandler(xmlPath)
await IO.WriteFile(jsonPath, JSON.stringify(data))
return data
} else {
const json = JSON.parse(await IO.ReadFile(jsonPath, 'utf-8'))
return json
}
}

9
gitadora@asphyxia/data/mdb/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
ex.xml
mt.xml
nt.xml
hv.xml
ex.json
mt.json
nt.json
hv.json
custom.xml

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,126 @@
export interface CommonMusicDataField {
id: KITEM<"s32">;
cont_gf: KITEM<"bool">;
cont_dm: KITEM<"bool">;
is_secret: KITEM<"bool">;
is_hot: KITEM<"bool">;
data_ver: KITEM<"s32">;
diff: KARRAY<"u16">;
}
export interface CommonMusicData {
music: CommonMusicDataField[]
}
export enum DATAVersion {
HIGHVOLTAGE = "hv",
NEXTAGE = "nt",
EXCHAIN = "ex",
MATTIX = "mt"
}
type processRawDataHandler = (path: string) => Promise<CommonMusicData>
export async function readXML(path: string) {
const xml = await IO.ReadFile(path, 'utf-8');
const json = U.parseXML(xml, false)
return json
}
export async function readJSON(path: string) {
const str = await IO.ReadFile(path, 'utf-8');
const json = JSON.parse(str)
return json
}
export async function readJSONOrXML(jsonPath: string, xmlPath: string, processHandler: processRawDataHandler): Promise<CommonMusicData> {
if (!IO.Exists(jsonPath)) {
const data = await processHandler(xmlPath)
await IO.WriteFile(jsonPath, JSON.stringify(data))
return data
} else {
const json = JSON.parse(await IO.ReadFile(jsonPath, 'utf-8'))
return json
}
}
export async function readB64JSON(b64path: string) {
const buff = await IO.ReadFile(b64path, 'utf-8');
return JSON.parse(Buffer.from(buff, 'base64').toString('utf-8'));
}
export function gameVerToDataVer(ver: string): DATAVersion {
switch(ver) {
case 'highvoltage':
return DATAVersion.HIGHVOLTAGE
case 'nextage':
return DATAVersion.NEXTAGE
case 'exchain':
return DATAVersion.EXCHAIN
case 'matixx':
default:
return DATAVersion.MATTIX
}
}
export async function processDataBuilder(gameVer: string, processHandler?: processRawDataHandler) {
const ver = gameVerToDataVer(gameVer)
const base = `data/mdb/${ver}`
if (IO.Exists(`${base}.b64`)) {
return await readB64JSON(`${base}.b64`);
}
const { music } = await readJSONOrXML(`${base}.json`, `${base}.xml`, processHandler ?? defaultProcessRawData)
// await IO.WriteFile(`${base}.b64`, Buffer.from(JSON.stringify({music})).toString("base64"))
return { music };
}
export async function defaultProcessRawData(path: string): Promise<CommonMusicData> {
const data = await readXML(path)
const mdb = $(data).elements("mdb.mdb_data");
const music: any[] = [];
for (const m of mdb) {
const d = m.numbers("xg_diff_list");
const contain = m.numbers("contain_stat");
const gf = contain[0];
const dm = contain[1];
if (gf == 0 && dm == 0) {
continue;
}
let type = gf;
if (gf == 0) {
type = dm;
}
music.push({
id: K.ITEM('s32', m.number("music_id")),
cont_gf: K.ITEM('bool', gf == 0 ? 0 : 1),
cont_dm: K.ITEM('bool', dm == 0 ? 0 : 1),
is_secret: K.ITEM('bool', 0),
is_hot: K.ITEM('bool', type == 2 ? 0 : 1),
data_ver: K.ITEM('s32', m.number("data_ver", 115)),
diff: K.ARRAY('u16', [
d[0],
d[1],
d[2],
d[3],
d[4],
d[10],
d[11],
d[12],
d[13],
d[14],
d[5],
d[6],
d[7],
d[8],
d[9],
]),
});
}
return {
music,
};
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,70 +1,25 @@
import { getVersion } from "../utils";
import { processData as ExchainMusic } from "../data/Exchain"
import { processData as MatixxMusic } from "../data/Matixx"
import { CommonMusicDataField, readJSONOrXML, readXML } from "../data/helper";
import { defaultProcessRawData, processDataBuilder } from "../data/mdb"
import { CommonMusicDataField, readJSONOrXML, readXML } from "../data/mdb";
import Logger from "../utils/logger"
const logger = new Logger("MusicList")
export const playableMusic: EPR = async (info, data, send) => {
const version = getVersion(info);
let music: CommonMusicDataField[] = [];
try {
if (U.GetConfig("enable_custom_mdb")) {
const data = await readXML('data/custom_mdb.xml')
const mdb = $(data).elements("mdb.mdb_data");
for (const m of mdb) {
const d = m.numbers("xg_diff_list");
const contain = m.numbers("contain_stat");
const gf = contain[0];
const dm = contain[1];
if (gf == 0 && dm == 0) {
continue;
}
let type = gf;
if (gf == 0) {
type = dm;
}
music.push({
id: K.ITEM('s32', m.number("music_id")),
cont_gf: K.ITEM('bool', gf == 0 ? 0 : 1),
cont_dm: K.ITEM('bool', dm == 0 ? 0 : 1),
is_secret: K.ITEM('bool', 0),
is_hot: K.ITEM('bool', type == 2 ? 0 : 1),
data_ver: K.ITEM('s32', m.number("data_ver", 115)),
diff: K.ARRAY('u16', [
d[0],
d[1],
d[2],
d[3],
d[4],
d[10],
d[11],
d[12],
d[13],
d[14],
d[5],
d[6],
d[7],
d[8],
d[9],
]),
});
}
music = (await defaultProcessRawData('data/mdb/custom.xml')).music
}
} catch (e) {
console.error(e.stack);
console.error("Fallback: Using default MDB method.")
logger.warn("Read Custom MDB failed. Using default MDB as a fallback.")
logger.debugWarn(e.stack);
music = [];
}
if (music.length == 0) {
if (version == 'exchain') {
music = _.get(await ExchainMusic(), 'music', []);
} else {
music = _.get(await MatixxMusic(), 'music', []);
}
music = _.get(await processDataBuilder(version), 'music', []);
}

View File

@@ -1,3 +1,5 @@
import { getEncoreStageData } from "../data/extrastage";
export const shopInfoRegist: EPR = async (info, data, send) => {
send.object({
data: {
@@ -19,6 +21,7 @@ export const gameInfoGet: EPR = async (info, data, send) => {
bonus_musicid: K.ITEM('s32', 0),
},
bear_fes: {},
nextadium: {},
};
const time = BigInt(31536000);
for (let i = 1; i <= 20; ++i) {
@@ -42,6 +45,18 @@ export const gameInfoGet: EPR = async (info, data, send) => {
term: K.ITEM('u8', 0),
sticker_list: {},
};
addition['thanksgiving'] = {
...obj,
box_term: {
state: K.ITEM('u8', 0)
}
};
addition['lotterybox'] = {
...obj,
box_term: {
state: K.ITEM('u8', 0)
}
};
} else {
addition[`phrase_combo_challenge_${i}`] = obj;
}
@@ -59,16 +74,20 @@ export const gameInfoGet: EPR = async (info, data, send) => {
}
}
const extraData = getEncoreStageData(info)
await send.object({
now_date: K.ITEM('u64', time),
now_date: K.ITEM('u64', BigInt(Date.now())),
extra: {
extra_lv: K.ITEM('u8', 10),
extra_lv: K.ITEM('u8', extraData.level),
extramusic: {
music: {
musicid: K.ITEM('s32', 0),
get_border: K.ITEM('u8', 0),
},
},
music: extraData.musics.map(mid => {
return {
musicid: K.ITEM('s32', mid),
get_border: K.ITEM('u8', 0),
}
})
}
},
infect_music: { term: K.ITEM('u8', 0) },
unlock_challenge: { term: K.ITEM('u8', 0) },

View File

@@ -45,6 +45,7 @@ export const check: EPR = async (info, data, send) => {
const playerInfo = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
version
})
if (playerInfo) {
@@ -90,6 +91,7 @@ export const getPlayer: EPR = async (info, data, send) => {
const name = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
version
})
const dmProfile = await getProfile(refid, version, 'dm')
const gfProfile = await getProfile(refid, version, 'gf')
@@ -388,12 +390,12 @@ export const getPlayer: EPR = async (info, data, send) => {
send.object({
player: K.ATTR({ 'no': `${no}` }, {
now_date: K.ITEM('u64', time),
secretmusic: {
secretmusic: { // TODO: FIX THIS
music: {
musicid: K.ITEM('s32', 0),
seq: K.ITEM('u16', 255),
kind: K.ITEM('s32', 40),
},
}
},
chara_list: {},
title_parts: {},
@@ -512,6 +514,20 @@ export const getPlayer: EPR = async (info, data, send) => {
unlock_status_6: K.ITEM('s32', 0),
unlock_status_7: K.ITEM('s32', 0),
},
thanksgiving: {
term: K.ITEM("u8", 0),
score: {
one_day_play_cnt: K.ITEM("s32", 0),
one_day_lottery_cnt: K.ITEM("s32", 0),
lucky_star: K.ITEM("s32", 0),
bear_mark: K.ITEM("s32", 0),
play_date_ms: K.ITEM("u64", BigInt(0))
},
lottery_result: {
unlock_bit: K.ITEM("u64", BigInt(0))
}
},
lotterybox: {},
...addition,
...playerData,
finish: K.ITEM('bool', 1),
@@ -910,7 +926,7 @@ export const savePlayer: EPR = async (info, data, send) => {
scores[mid].update[1] = newSkill;
}
scores[mid].diffs[seq] = {
scores[mid].diffs[seq] = { //FIXME: Real server is bit complicated. this one is too buggy.
perc: Math.max(_.get(scores[mid].diffs[seq], 'perc', 0), perc),
rank: Math.max(_.get(scores[mid].diffs[seq], 'rank', 0), rank),
meter: meter.toString(),

View File

@@ -2,23 +2,42 @@ import { gameInfoGet, shopInfoRegist } from "./handlers/info";
import { playableMusic } from "./handlers/MusicList"
import { getPlayer, check, regist, savePlayer } from "./handlers/profiles";
import { updatePlayerInfo } from "./handlers/webui";
import { isRequiredVersion } from "./utils";
import { isAsphyxiaDebugMode, isRequiredCoreVersion } from "./utils";
import Logger from "./utils/logger";
const logger = new Logger("main")
export function register() {
if(!isRequiredVersion(1, 20)) {
if(!isRequiredCoreVersion(1, 20)) {
console.error("You need newer version of Core. v1.20 or newer required.")
}
R.GameCode('M32');
R.Config("encore_version", {
name: "Encore Version",
desc: "Set encore version",
type: "integer",
default: 13,
})
R.Config("nextage_dummy_encore", {
name: "Dummy Encore for SPE (Nextage Only)",
desc: "Since Nextage's Special Premium Encore system is bit complicated, \n"
+ "SPE System isn't fully implemented. \n"
+ "This thing is bandage of these problem as limiting some Encores for SPE.",
type: "boolean",
default: false
})
R.Config("enable_custom_mdb", {
name: "Enable Custom MDB",
desc: "For who uses own MDB",
desc: "For who uses own MDB. eg) Omnimix.",
type: "boolean",
default: false,
})
R.DataFile("data/custom_mdb.xml", {
R.DataFile("data/mdb/custom.xml", {
accept: ".xml",
name: "Custom MDB",
desc: "You need to enable Custom MDB option first."
@@ -30,7 +49,7 @@ export function register() {
// Helper for register multiple versions.
R.Route(`exchain_${method}`, handler);
R.Route(`matixx_${method}`, handler);
// TODO: NEXTAGE
R.Route(`nextage_${method}`, handler)
// TODO: TB, TBRE and more older version?
};
@@ -46,4 +65,13 @@ export function register() {
MultiRoute('cardutil.check', check);
MultiRoute('gametop.get', getPlayer);
MultiRoute('gameend.regist', savePlayer);
// Misc
R.Route('bemani_gakuen.get_music_info', true)
R.Unhandled(async (info, data, send) => {
if (["eventlog"].includes(info.module)) return;
logger.error(`Received Unhandled Request on Method "${info.method}" by ${info.model}/${info.module}`)
logger.debugError(`Received Request: ${JSON.stringify(data, null, 4)}`)
})
}

View File

@@ -2,7 +2,7 @@ export interface Scores {
collection: 'scores';
game: 'gf' | 'dm';
version: string;
version?: string;
pluginVer: number
scores: {

View File

@@ -11,9 +11,14 @@ export const getVersion = (info: EamuseInfo) => {
return moduleName.match(/([^_]*)_(.*)/)[1];
};
export function isRequiredVersion(major: number, minor: number) {
export function isRequiredCoreVersion(major: number, minor: number) {
// version value exposed since Core v1.19
const core_major = typeof CORE_VERSION_MAJOR === "number" ? CORE_VERSION_MAJOR : 1
const core_minor = typeof CORE_VERSION_MINOR === "number" ? CORE_VERSION_MINOR : 18
return core_major >= major && core_minor >= minor
return core_major > major || (core_major === major && core_minor >= minor)
};
export function isAsphyxiaDebugMode() {
const argv = process.argv
return argv.includes("--dev") || argv.includes("--console")
}

View File

@@ -0,0 +1,62 @@
import { isAsphyxiaDebugMode } from ".";
export default class Logger {
public category: string | null;
public constructor(category?: string) {
this.category = (category == null) ? null : `[${category}]`
}
public error(...args: any[]) {
this.argsHandler(console.error, ...args)
}
public debugError(...args: any[]) {
if (isAsphyxiaDebugMode()) {
this.argsHandler(console.error, ...args)
}
}
public warn(...args: any[]) {
this.argsHandler(console.warn, ...args)
}
public debugWarn(...args: any[]) {
if (isAsphyxiaDebugMode()) {
this.argsHandler(console.warn, ...args)
}
}
public info(...args: any[]) {
this.argsHandler(console.info, ...args)
}
public debugInfo(...args: any[]) {
if (isAsphyxiaDebugMode()) {
this.argsHandler(console.info, ...args)
}
}
public log(...args: any[]) {
this.argsHandler(console.log, ...args)
}
public debugLog(...args: any[]) {
if (isAsphyxiaDebugMode()) {
this.argsHandler(console.log, ...args)
}
}
private argsHandler(target: Function, ...args: any[]) {
if (this.category == null) {
target(...args)
} else {
target(this.category, ...args)
}
}
}

View File

@@ -1,6 +1,6 @@
# Jubeat
Plugin Version: **v1.0.0**
Plugin Version: **v1.5.0**
### Supported Versions
@@ -8,11 +8,36 @@ Plugin Version: **v1.0.0**
- knit
- knit APPEND
- copious
- copious APPEND
- saucer
- saucer fulfill
### Changelogs
***
#### 1.5.0
- saucer fulfill support
#### 1.4.1
- saucer support
- Change profile structure
#### 1.3.0
- Matching Support (Experimental)
#### 1.2.0
- copious (APPEND) support
#### 1.1.0
- Fix profile structure
#### 1.0.0
- Initial Release

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,39 @@
import {getVersion} from "../utils";
import {getVersion, VersionRange} from "../utils";
export const shopinfo: EPR = (info, data, send) => {
const locId = $(data.shop).content("locationid");
export const gameInfo: EPR = (info, data, send) => {
const locId = $(data).content("shop.locationid");
const version = getVersion(info);
if (version === 0) return send.deny();
if (version === 3) return send.object({
return send.object({
data: {
cabid: K.ITEM('u32', 1),
locationid: K.ITEM('str', locId),
is_send: K.ITEM("u8", 1)
}
})
...info.module === "shopinfo" && {
cabid: K.ITEM("u32", _.random(1, 10)),
locationid: K.ITEM("str", locId),
...VersionRange(version, 3, 6) && { is_send: K.ITEM("u8", 1) },
tax_phase: K.ITEM("u8", 0),
facility: {
exist: K.ITEM("u32", 1)
}
},
return send.deny();
}
...VersionRange(version, 5, 6) && {
white_music_list: K.ARRAY("s32", Array(32).fill(-1))
}
}
});
};
export const demodata = {
getNews: (_, __, send) => send.object({ data: { officialnews: K.ATTR({ count: "0" }) } }),
getData: (_, data, send) => {
const newsId = $(data).number('officialnews.newsid');
const newsId = $(data).number("officialnews.newsid");
return send.object({
data: {
officialnews: {
data: {
newsid: K.ITEM('s16', newsId),
image: K.ITEM('u8', 0, { size: '0' })
newsid: K.ITEM("s16", newsId),
image: K.ITEM("u8", 0, { size: "0" })
}
}
}
@@ -34,7 +42,7 @@ export const demodata = {
getHitchart: (_, __, send) => send.object({
data: {
hitchart: {
update: K.ITEM('str', ''),
update: K.ITEM("str", ""),
hitchart_lic: K.ATTR({ count: "0" }),
hitchart_org: K.ATTR({ count: "0" }),
@@ -42,9 +50,3 @@ export const demodata = {
}
}),
};
export const netlog: EPR = (info, data, send) => {
const errMsg = $(data).str('msg');
console.error(errMsg);
return send.success();
}

View File

@@ -1,49 +1,147 @@
export const check: EPR = (info, data, send) => {
import {Room} from '../models/matching';
export const check: EPR = async (info, data, send) => {
const enter = $(data).bool('data.enter');
const time = $(data).number('data.time');
// enter
// 0 - game is loading
// 1 - music select screen
return send.object({
data: {
entrant_nr: K.ITEM('u32', 1, { time: String(time) }),
interval: K.ITEM('s16', 1),
entry_timeout: K.ITEM('s16', U.GetConfig("matching_entry_timeout")),
waitlist: K.ATTR({ count: "0" })
interval: K.ITEM('s16', 5),
entry_timeout: K.ITEM('s16', 30),
waitlist: K.ATTR({ count: '0' })
}
});
};
export const entry: EPR = (info, data, send) => {
const localMatchingNode = $(data).element("data.local_matching");
const connectNode = $(data).element("data.connect");
export const entry: EPR = async (info, data, send) => {
const localMatchingNode = $(data).element('data.local_matching');
const connectNode = $(data).element('data.connect');
const musicNode = $(data).element('data.music');
const roomId = _.random(1, 999999999999999);
const localKey = localMatchingNode.numbers('key');
const connectKey = connectNode.numbers('key');
// TODO Local matching support
let matchRoom = await DB.FindOne<Room>({
collection: 'matching_rooms',
musicId: musicNode.number('id'),
seqId: musicNode.number('seq'),
isMatchEnd: false,
isFull: false
});
if (!matchRoom) {
matchRoom = {
collection: 'matching_rooms',
version: $(data).number('data.version'),
roomId: _.random(1, 999999999),
masterKey: connectKey,
masterGlobal: connectNode.str('global'),
masterPrivate: connectNode.str('private'),
localKey,
musicId: musicNode.number('id'),
seqId: musicNode.number('seq'),
members: [
{
cabid: $(data).number('data.cabid'),
addr: connectNode.str('private')
}
],
isFull: false,
isMatchEnd: false
};
await DB.Upsert<Room>({
collection: 'matching_rooms', localKey,
musicId: musicNode.number('id'),
seqId: musicNode.number('seq'),
isMatchEnd: false,
isFull: false
}, matchRoom);
}
return send.object({
data: {
roomid: K.ITEM('s64', BigInt(roomId), { master: "1" }),
refresh_intr: K.ITEM('s16', 3),
roomid: K.ITEM('s64', BigInt(matchRoom.roomId), { master: matchRoom.masterKey === connectKey ? '1' : '0' }),
...matchRoom.masterKey === connectKey && {
refresh_intr: K.ITEM('s16', 10),
},
...matchRoom.masterKey !== connectKey && {
connect: {
key: K.ARRAY('u8', matchRoom.masterKey),
global: K.ITEM('str', matchRoom.masterGlobal),
private: K.ITEM('str', matchRoom.masterPrivate),
}
},
music: {
id: K.ITEM("u32", musicNode.number("id")),
seq: K.ITEM("u8", musicNode.number("seq")),
id: K.ITEM('u32', matchRoom.musicId),
seq: K.ITEM('u8', matchRoom.seqId),
}
}
});
};
export const refresh: EPR = (info, data, send) => {
export const refresh: EPR = async (info, data, send) => {
const roomId = Number($(data).bigint('data.roomid'));
const pcbinfos = $(data).elements('data.joined.pcbinfo');
const room = await DB.FindOne<Room>({ collection: 'matching_rooms', roomId });
if (room) {
for (const i of pcbinfos) {
const cabid = i.number('cabid');
const addr = i.str('addr');
for (const i of room.members) {
if (i.addr === addr) continue;
room.members.push({
cabid,
addr
});
}
}
await DB.Update<Room>({ collection: 'matching_rooms', roomId: Number(roomId) }, {
$set: {
members: room.members
}
});
if (room.members.length >= 4) {
await DB.Update<Room>({ collection: 'matching_rooms', roomId: Number(roomId) }, {
$set: {
isFull: true
}
});
}
}
return send.object({
data: {
refresh_intr: K.ITEM('s16', 2),
refresh_intr: K.ITEM('s16', 5),
start: K.ITEM('bool', room.isFull)
}
});
};
export const report: EPR = (info, data, send) => {
export const report: EPR = async (info, data, send) => {
const roomId = $(data).bigint('data.roomid');
await DB.Update<Room>({ collection: 'matching_rooms', roomId: Number(roomId) }, {
$set: {
isMatchEnd: true
}
});
return send.object({
data: {
refresh_intr: K.ITEM('s16', 1),
refresh_intr: K.ITEM('s16', 3),
}
});
};

View File

@@ -1,6 +1,7 @@
import {getVersion} from "../utils";
import Profile from '../models/profile';
import {Score} from '../models/score';
import {getVersion, getVersionName, VersionRange} from "../utils";
import Profile from "../models/profile";
import {Score} from "../models/score";
import {CourseResult} from "../models/course";
export const profile: EPR = async (info, data, send) => {
let refId = $(data).str("data.player.pass.refid");
@@ -16,155 +17,298 @@ export const profile: EPR = async (info, data, send) => {
if (!profile) {
if (!name) return send.deny();
const newProfile = new Profile();
newProfile.jubeatId = _.random(1, 99999999);
newProfile.name = name;
newProfile.previous_version = version;
const newProfile: Profile = {
collection: "profile",
jubeatId: _.random(1, 99999999),
name: name,
lastShopname: "NONE",
lastAreaname: "NONE"
};
await DB.Upsert<Profile>(refId, { collection: "profile" }, newProfile);
profile = newProfile;
}
let migration = false;
if (profile.previous_version < version) {
migration = true;
profile.name = "";
await DB.Update<Profile>(refId, { collection: "profile" }, { $set: { name: "", previous_version: version } });
}
return send.object({
data: {
...version === 5 && require("../templates/gameInfos/saucer.ts")(profile),
...version === 6 && require("../templates/gameInfos/fulfill.ts")(profile),
if (name) {
profile.name = name;
await DB.Update<Profile>(refId, { collection: "profile" }, { $set: { name } });
}
player: {
name: K.ITEM("str", profile.name),
jid: K.ITEM("s32", profile.jubeatId),
refid: K.ITEM("str", profile.__refid),
session_id: K.ITEM("s32", 1),
event_flag: K.ITEM("u64", BigInt(0)),
if (version === 3) {
if (U.GetConfig("unlock_all_songs")) {
profile.knit.item = {
secretList: [-1, -1],
themeList: -1,
markerList: [-1, -1],
titleList: [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
};
profile.knit.item_new = {
secretList: [0, 0],
themeList: 0,
markerList: [0, 0],
titleList: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
};
...version === 3 && require("../templates/profiles/knit.ts")(profile),
...version === 4 && require("../templates/profiles/copious.ts")(profile),
...version === 5 && require("../templates/profiles/saucer.ts")(profile),
...version === 6 && require("../templates/profiles/fulfill.ts")(profile),
}
}
return send.pugFile('templates/knit/profile.pug', { refId, migration, ...profile }, { compress: false });
}
return send.deny();
});
};
export const saveProfile: EPR = async (info, { data }, send) => {
console.log(U.toXML(data));
const player = $(data).element("player");
console.log(U.toXML({
call: K.ATTR({ model: info.model }, {
[info.module]: K.ATTR({ method: info.method }, { data })
})
}));
const refId = player.str("refid");
const refId = $(data).str("player.refid");
if (!refId) return send.deny();
const version = getVersion(info);
if (version === 0) return send.deny();
const profile = await DB.FindOne<Profile>(refId, { collection: "profile" });
if (!profile) return send.deny();
if (version === 3) {
profile.name = player.str("name");
let lastMarker = 0;
let lastTheme = 0;
let lastTitle = 0;
let lastParts = 0;
let lastSort = 0;
let lastFilter = 0;
let lastCategory = 0;
let lastMselStat = 0;
profile.last.shopname = player.str("shopname", profile.last.shopname);
profile.last.areaname = player.str("areaname", profile.last.areaname);
const result = $(data).element("result");
profile.jubility = player.number("info.jubility", profile.jubility);
profile.jubilityYday = player.number("info.jubility_yday", profile.jubilityYday);
if (result) {
const tunes = result.elements("tune");
const historys = {};
const historyNode = $(data).elements("player.history.tune");
profile.knit.acvProg = player.number("info.acv_prog", profile.knit.acvProg);
profile.knit.acvWool = player.number("info.acv_wool", profile.knit.acvWool);
profile.knit.acvRouteProg = player.numbers("info.acv_route_prog", profile.knit.acvRouteProg);
profile.knit.acvPoint = player.number("info.acv_point", profile.knit.acvPoint);
profile.tuneCount = player.number("info.tune_cnt", profile.tuneCount);
profile.saveCount = player.number("info.save_cnt", profile.saveCount);
profile.savedCount = player.number("info.saved_cnt", profile.savedCount);
profile.fullcomboCount = player.number("info.fc_cnt", profile.fullcomboCount);
profile.fullcomboSeqCount = player.number("info.fc_seq_cnt", profile.fullcomboSeqCount);
profile.excellentCount = player.number("info.exc_cnt", profile.excellentCount);
profile.excellentSeqCount = player.number("info.exc_seq_cnt", profile.excellentSeqCount);
profile.matchCount = player.number("info.match_cnt", profile.matchCount);
profile.beatCount = player.number('info.beat_cnt', profile.beatCount);
profile.conciergeSelectedCount = player.number('info.con_sel_cnt', profile.conciergeSelectedCount);
profile.tagCount = player.number('info.tag_cnt', profile.tagCount);
profile.mynewsCount = player.number('info.mynews_cnt', profile.mynewsCount);
if (!U.GetConfig("unlock_all_songs")) {
profile.knit.item.secretList = player.numbers('item.secret_list', profile.knit.item.secretList);
profile.knit.item.themeList = player.number('item.theme_list', profile.knit.item.themeList);
profile.knit.item.markerList = player.numbers('item.marker_list', profile.knit.item.markerList);
profile.knit.item.titleList = player.numbers('item.title_list', profile.knit.item.titleList);
profile.knit.item_new.secretList = player.numbers('item.secret_new', profile.knit.item_new.secretList);
profile.knit.item_new.themeList = player.number('item.theme_new', profile.knit.item_new.themeList);
profile.knit.item_new.markerList = player.numbers('item.marker_new', profile.knit.item_new.markerList);
profile.knit.item_new.titleList = player.numbers('item.title_new', profile.knit.item_new.titleList);
}
profile.last.conciergeSuggestId = player.number('info.con_suggest_id', profile.last.conciergeSuggestId);
profile.last.playTime = BigInt(new Date().getMilliseconds());
// Append
const collabo = player.element("collabo");
if (collabo) {
profile.knit.collabo.success = collabo.bool("success");
profile.knit.collabo.completed = collabo.bool("completed");
}
const result = $(data).element("result");
if (result) {
const tunes = result.elements("tune");
for (const tune of tunes) {
const musicId = tune.number("music", 0);
profile.last.musicId = musicId;
profile.last.seqId = parseInt(tune.attr("player.score").seq) || 0;
profile.last.title = tune.number("title", profile.last.title);
profile.last.theme = tune.number("theme", profile.last.theme);
profile.last.marker = tune.number("marker", profile.last.marker);
profile.last.sort = tune.number("sort", profile.last.sort);
profile.last.filter = tune.number("filter", profile.last.filter);
profile.last.showRank = tune.number("combo_disp", profile.last.showRank);
profile.last.showCombo = tune.number("rank_sort", profile.last.showCombo);
profile.last.mselStat = tune.number("msel_stat", profile.last.mselStat);
const score = tune.number('player.score');
const seq = parseInt(tune.attr('player.score').seq);
const clear = parseInt(tune.attr('player.score').clear);
const combo = parseInt(tune.attr('player.score').combo);
const bestScore = tune.number('player.best_score');
const bestClear = tune.number('player.best_clear');
const playCount = tune.number('player.play_cnt');
const clearCount = tune.number('player.clear_cnt');
const fullcomboCount = tune.number('player.fc_cnt');
const excellentCount = tune.number('player.exc_cnt');
const mbar = tune.numbers('player.mbar');
await updateScore(refId, musicId, seq, score, clear, mbar, {
playCount,
clearCount,
fullcomboCount,
excellentCount
});
if (historyNode) {
for (const history of historyNode) {
historys[history.attr().log_id] = {
timestamp: history.bigint("timestamp"),
isHard: history.bool("player.result.is_hard_mode")
};
}
}
await DB.Update<Profile>(refId, { collection: "profile" }, profile);
for (const tune of tunes) {
const tuneId = tune.attr().id;
return send.object({ data: { player: { session_id: K.ITEM('s32', 1) } } });
profile.musicId = tune.number("music");
profile.seqId = parseInt(tune.attr("player.score").seq);
lastMarker = tune.number("marker");
lastTheme = tune.number("theme");
lastTitle = tune.number("title");
lastParts = tune.number("parts");
lastSort = tune.number("sort");
lastFilter = tune.number("filter");
lastCategory = tune.number("category");
lastMselStat = tune.number("msel_stat");
profile.rankSort = tune.number("rank_sort");
profile.comboDisp = tune.number("combo_disp");
await updateScore(refId, {
musicId: tune.number("music"),
seq: parseInt(tune.attr("player.score").seq),
score: tune.number("player.score"),
clear: parseInt(tune.attr("player.score").clear),
isHard: historys[tuneId]?.isHard || false,
bestScore: tune.number("player.best_score"),
bestClear: tune.number("player.best_clear"),
playCount: tune.number("player.play_cnt"),
clearCount: tune.number("player.clear_cnt"),
fullcomboCount: tune.number("player.fc_cnt"),
excellentCount: tune.number("player.exc_cnt"),
...tune.element("player.mbar") && { mbar: tune.numbers("player.mbar") }
});
}
}
return send.deny();
profile.lastPlayTime = Number($(data).bigint("player.time_gameend"));
profile.lastShopname = $(data).str("player.shopname");
profile.lastAreaname = $(data).str("player.areaname");
if (version === 3) {
if (!profile.knit) profile.knit = {};
profile.knit.jubility = $(data).number("player.info.jubility");
profile.knit.jubilityYday = $(data).number("player.info.jubility_yday");
profile.knit.acvProg = $(data).number("player.info.acv_prog");
profile.knit.acvPoint = $(data).number("player.info.acv_point");
profile.knit.acvWool = $(data).number("player.info.acv_wool");
profile.knit.acvRouteProg = $(data).numbers("player.info.acv_route_prog");
profile.knit.tuneCount = $(data).number("player.info.tune_cnt");
profile.knit.saveCount = $(data).number("player.info.save_cnt");
profile.knit.savedCount = $(data).number("player.info.saved_cnt");
profile.knit.fcCount = $(data).number("player.info.fc_cnt");
profile.knit.fcSeqCount = $(data).number("player.info.fc_seq_cnt");
profile.knit.exCount = $(data).number("player.info.exc_cnt");
profile.knit.exSeqCount = $(data).number("player.info.exc_seq_cnt");
profile.knit.matchCount = $(data).number("player.info.match_cnt");
profile.knit.conSelCount = $(data).number("player.info.con_sel_cnt");
profile.knit.marker = lastMarker;
profile.knit.theme = lastTheme;
profile.knit.title = lastTitle;
profile.knit.sort = lastSort;
profile.knit.filter = lastFilter;
profile.knit.mselStat = lastMselStat;
profile.knit.conSuggestId = $(data).number("player.info.con_suggest_id");
profile.knit.secretList = $(data).numbers("player.item.secret_list");
profile.knit.themeList = $(data).number("player.item.theme_list");
profile.knit.markerList = $(data).numbers("player.item.marker_list");
profile.knit.titleList = $(data).numbers("player.item.title_list");
profile.knit.secretListNew = $(data).numbers("player.item.secret_new");
profile.knit.themeListNew = $(data).number("player.item.theme_new");
profile.knit.markerListNew = $(data).numbers("player.item.marker_new");
profile.knit.titleListNew = $(data).numbers("player.item.title_new");
}
if (version === 4) {
if (!profile.copious) profile.copious = {};
profile.copious.jubility = $(data).number("player.info.jubility");
profile.copious.jubilityYday = $(data).number("player.info.jubility_yday");
profile.copious.acvState = $(data).number("player.info.acv_state");
profile.copious.acvPoint = $(data).number("player.info.acv_point");
profile.copious.acvOwn = $(data).number("player.info.acv_own");
profile.copious.acvThrow = $(data).numbers("player.info.acv_throw");
profile.copious.tuneCount = $(data).number("player.info.tune_cnt");
profile.copious.saveCount = $(data).number("player.info.save_cnt");
profile.copious.savedCount = $(data).number("player.info.saved_cnt");
profile.copious.fcCount = $(data).number("player.info.fc_cnt");
profile.copious.fcSeqCount = $(data).number("player.info.fc_seq_cnt");
profile.copious.exCount = $(data).number("player.info.exc_cnt");
profile.copious.exSeqCount = $(data).number("player.info.exc_seq_cnt");
profile.copious.matchCount = $(data).number("player.info.match_cnt");
profile.copious.totalBestScore = $(data).number("player.info.total_best_score");
profile.copious.marker = lastMarker;
profile.copious.theme = lastTheme;
profile.copious.title = lastTitle;
profile.copious.parts = lastParts;
profile.copious.sort = lastSort;
profile.copious.category = lastCategory;
profile.copious.mselStat = lastMselStat;
profile.copious.secretList = $(data).numbers("player.item.secret_list");
profile.copious.themeList = $(data).number("player.item.theme_list");
profile.copious.markerList = $(data).numbers("player.item.marker_list");
profile.copious.titleList = $(data).numbers("player.item.title_list");
profile.copious.partsList = $(data).numbers("player.item.parts_list");
profile.copious.secretListNew = $(data).numbers("player.item.secret_new");
profile.copious.themeListNew = $(data).number("player.item.theme_new");
profile.copious.markerListNew = $(data).numbers("player.item.marker_new");
profile.copious.titleListNew = $(data).numbers("player.item.title_new");
}
if (version === 5) {
if (!profile.saucer) profile.saucer = {};
profile.saucer.jubility = $(data).number("player.info.jubility");
profile.saucer.jubilityYday = $(data).number("player.info.jubility_yday");
profile.saucer.tuneCount = $(data).number("player.info.tune_cnt");
profile.saucer.clearCount = $(data).number("player.info.clear_cnt");
profile.saucer.saveCount = $(data).number("player.info.save_cnt");
profile.saucer.savedCount = $(data).number("player.info.saved_cnt");
profile.saucer.fcCount = $(data).number("player.info.fc_cnt");
profile.saucer.exCount = $(data).number("player.info.exc_cnt");
profile.saucer.matchCount = $(data).number("player.info.match_cnt");
profile.saucer.totalBestScore = $(data).number("player.info.total_best_score");
profile.saucer.marker = lastMarker;
profile.saucer.theme = lastTheme;
profile.saucer.title = lastTitle;
profile.saucer.parts = lastParts;
profile.saucer.sort = lastSort;
profile.saucer.category = lastCategory;
profile.saucer.secretList = $(data).numbers("player.item.secret_list");
profile.saucer.themeList = $(data).number("player.item.theme_list");
profile.saucer.markerList = $(data).numbers("player.item.marker_list");
profile.saucer.titleList = $(data).numbers("player.item.title_list");
profile.saucer.partsList = $(data).numbers("player.item.parts_list");
profile.saucer.secretListNew = $(data).numbers("player.item.secret_new");
profile.saucer.themeListNew = $(data).number("player.item.theme_new");
profile.saucer.markerListNew = $(data).numbers("player.item.marker_new");
profile.saucer.titleListNew = $(data).numbers("player.item.title_new");
if (!profile.saucer.bistro) profile.saucer.bistro = {};
profile.saucer.bistro.carry_over = $(data).number("player.bistro.carry_over");
}
if (version === 6) {
if (!profile.fulfill) profile.fulfill = {};
profile.fulfill.jubility = $(data).number("player.info.jubility");
profile.fulfill.jubilityYday = $(data).number("player.info.jubility_yday");
profile.fulfill.tuneCount = $(data).number("player.info.tune_cnt");
profile.fulfill.saveCount = $(data).number("player.info.save_cnt");
profile.fulfill.savedCount = $(data).number("player.info.saved_cnt");
profile.fulfill.fcCount = $(data).number("player.info.fc_cnt");
profile.fulfill.exCount = $(data).number("player.info.exc_cnt");
profile.fulfill.clearCount = $(data).number("player.info.clear_cnt");
profile.fulfill.matchCount = $(data).number("player.info.match_cnt");
profile.fulfill.expertOption = $(data).number("player.info.expert_option");
profile.fulfill.matching = $(data).number("player.info.matching");
profile.fulfill.hazard = $(data).number("player.info.hazard");
profile.fulfill.hard = $(data).number("player.info.hard");
profile.fulfill.extraPoint = $(data).number("player.info.extra_point");
profile.fulfill.isExtraPlayed = $(data).bool("player.info.is_extra_played");
profile.fulfill.totalBestScore = $(data).number("player.info.total_best_score");
profile.fulfill.clearMaxLevel = $(data).number("player.info.clear_max_level");
profile.fulfill.fcMaxLevel = $(data).number("player.info.fc_max_level");
profile.fulfill.exMaxLevel = $(data).number("player.info.exc_max_level");
profile.fulfill.marker = lastMarker;
profile.fulfill.theme = lastTheme;
profile.fulfill.title = lastTitle;
profile.fulfill.parts = lastParts;
profile.fulfill.sort = lastSort;
profile.fulfill.category = lastCategory;
profile.fulfill.secretList = $(data).numbers("player.item.secret_list");
profile.fulfill.themeList = $(data).number("player.item.theme_list");
profile.fulfill.markerList = $(data).numbers("player.item.marker_list");
profile.fulfill.titleList = $(data).numbers("player.item.title_list");
profile.fulfill.partsList = $(data).numbers("player.item.parts_list");
profile.fulfill.secretListNew = $(data).numbers("player.item.secret_new");
profile.fulfill.themeListNew = $(data).number("player.item.theme_new");
profile.fulfill.markerListNew = $(data).numbers("player.item.marker_new");
profile.fulfill.titleListNew = $(data).numbers("player.item.title_new");
const courseNode = $(data).element("course");
if (courseNode) {
profile.fulfill.lastCourseId = courseNode.number("course_id");
await DB.Upsert<CourseResult>(refId, {
collection: "course_results",
courseId: courseNode.number("course_id"),
version: 6
}, {
$set: {
rating: courseNode.number("rating"),
scores: courseNode.elements("music").map(m => m.number("score"))
}
});
}
}
try {
await DB.Update<Profile>(refId, { collection: "profile" }, profile);
return send.object({
data: {
player: { session_id: K.ITEM("s32", 1) },
...version === 4 && { collabo: { deller: K.ITEM("s32", 0) } }
}
});
} catch (e) {
console.error(`Profile save failed: ${e.message}`);
return send.deny();
}
};
export const loadScore: EPR = async (info, data, send) => {
@@ -178,7 +322,7 @@ export const loadScore: EPR = async (info, data, send) => {
if (version === 0) return send.deny();
const scores = await DB.Find<Score>(profile.__refid, { collection: "score" });
const scoreData: { [musicId: number]: any } = {};
const scoreData: { [musicId: number]: { score: number[], clear: number[], playCnt: number[], clearCnt: number[], fcCnt: number[], exCnt: number[], bar: number[][] } } = {};
for (const score of scores) {
if (!scoreData[score.musicId]) {
@@ -198,95 +342,175 @@ export const loadScore: EPR = async (info, data, send) => {
data.clearCnt[score.seq] = score.clearCount;
data.fcCnt[score.seq] = score.fullcomboCount;
data.exCnt[score.seq] = score.excellentCount;
data.clear[score.seq] = score.clearType;
data.clear[score.seq] = score.clear;
data.score[score.seq] = score.score;
data.bar[score.seq] = score.bar;
}
if (version === 3) return send.object({
return send.object({
data: {
player: {
playdata: K.ATTR({ count: String(Object.keys(scoreData).length) }, {
musicdata: (() => {
const musicdata = [];
Object.entries(scoreData).forEach(([k, v]) => {
musicdata.push(K.ATTR({ music_id: String(k) }, {
play_cnt: K.ARRAY('s32', v.playCnt),
clear_cnt: K.ARRAY('s32', v.clearCnt),
fc_cnt: K.ARRAY('s32', v.fcCnt),
ex_cnt: K.ARRAY('s32', v.exCnt),
clear: K.ARRAY('s8', v.clear),
score: K.ARRAY('s32', v.score),
bar: v.bar.map((v, i) => K.ARRAY('u8', v, { seq: String(i) }))
}));
});
return musicdata;
})()
})
jid: K.ITEM("s32", jubeatId),
...version >= 3 && {
playdata: K.ATTR({ count: String(Object.keys(scoreData).length) }, {
musicdata: Object.keys(scoreData).map(musicId => K.ATTR({ music_id: String(musicId) }, {
score: K.ARRAY("s32", scoreData[musicId].score),
clear: K.ARRAY("s8", scoreData[musicId].clear),
play_cnt: K.ARRAY("s32", scoreData[musicId].playCnt),
clear_cnt: K.ARRAY("s32", scoreData[musicId].clearCnt),
fc_cnt: K.ARRAY("s32", scoreData[musicId].fcCnt),
ex_cnt: K.ARRAY("s32", scoreData[musicId].exCnt),
bar: scoreData[musicId].bar.map((bar, seq) => K.ARRAY("u8", bar, { seq: String(seq) }))
}))
})
}
}
}
});
return send.deny();
};
const updateScore = async (refId: string, musicId: number, seq: number, score: number, clear: number, mbar: number[], data: any) => {
let raised;
const updateScore = async (refId: string, data: any): Promise<boolean> => {
try {
await DB.Upsert<Score>(refId, {
collection: "score",
musicId: data.musicId,
seq: data.seq,
isHardMode: data.isHard
}, {
$set: {
musicId: data.musicId,
seq: data.seq,
score: data.bestScore,
clear: data.bestClear,
musicRate: 0,
...data.mbar && { bar: data.mbar, },
playCount: data.playCount,
clearCount: data.clearCount,
fullcomboCount: data.fullcomboCount,
excellentCount: data.excellentCount,
isHardMode: data.isHard
}
});
const oldScore = await DB.FindOne<Score>(refId, { collection: "score", musicId, seq });
return true;
} catch (e) {
console.error("Score saving failed: ", e.stack);
return false;
}
};
let scoreData = oldScore;
export const getCourse: EPR = async (info, data, send) => {
const version = getVersion(info);
if (version === 0) return send.deny();
if (!oldScore) {
scoreData = new Score();
scoreData.musicId = musicId;
scoreData.seq = seq;
raised = true;
} else {
raised = score > oldScore.score;
score = Math.max(oldScore.score, score);
const jubeatId = $(data).number("data.player.jid");
if (!jubeatId) return send.deny();
const profile = await DB.FindOne<Profile>(null, { collection: "profile", jubeatId });
if (!profile) return send.deny();
if (version === 6) {
const results = await DB.Find<CourseResult>(profile.__refid, { collection: "course_results", version: 6 });
const { courses } = require("../data/fulfill_courses.json");
const validCourseIds: number[] = courses.map(course => course.course_id);
return send.object({
data: {
course_list: {
course: courses.map(course => ({
id: K.ITEM("s32", course.course_id),
name: K.ITEM("str", course.course_name),
level: K.ITEM("u8", course.course_level),
norma: {
norma_id: K.ARRAY("s32", course.norma.norma_id),
bronze_value: K.ARRAY("s32", course.norma.bronze),
silver_value: K.ARRAY("s32", course.norma.silver),
gold_value: K.ARRAY("s32", course.norma.gold)
},
music_list: {
music: course.music_list.map(music => (K.ATTR({ index: music.index }, {
music_id: K.ITEM("s32", music.music_id),
seq: K.ITEM("u8", music.seq_id)
})))
}
}))
},
player_list: {
player: {
jid: K.ITEM("s32", jubeatId),
result_list: {
result: results.filter(e => validCourseIds.find(valid => valid === e.courseId)).map(result => ({
id: K.ITEM("s32", result.courseId),
rating: K.ITEM("u8", result.rating),
score: K.ARRAY("s32", result.scores)
}))
}
}
},
last_course_id: K.ITEM("s32", profile.fulfill?.lastCourseId || 0)
}
});
}
scoreData.clearType = Math.max(scoreData.clearType, clear);
scoreData.playCount = data.playCount;
scoreData.clearCount = data.clearCount;
scoreData.fullcomboCount = data.fullcomboCount;
scoreData.excellentCount = data.excellentCount;
scoreData.isHardmodeClear = false;
if (mbar && raised) {
scoreData.score = score;
scoreData.bar = mbar;
}
await DB.Upsert(refId, { collection: "score", musicId, seq }, scoreData);
return send.deny();
};
export const meeting: EPR = (info, data, send) => {
return send.object({
data: {
meeting: {
single: K.ATTR({ count: '0' }),
tag: K.ATTR({ count: '0' }),
single: K.ATTR({ count: "0" }),
tag: K.ATTR({ count: "0" }),
},
reward: {
total: K.ITEM('s32', 0),
point: K.ITEM('s32', 0)
total: K.ITEM("s32", 0),
point: K.ITEM("s32", 0)
}
}
});
};
export const getCollabo: EPR = (info, data, send) => send.object({
data: {
collabo: {
played: {
iidx: K.ITEM("s8", 1),
popn: K.ITEM("s8", 1),
ddr: K.ITEM("s8", 1),
reflec: K.ITEM("s8", 1),
gfdm: K.ITEM("s8", 1),
export const getCollabo: EPR = (info, data, send) => {
const version = getVersion(info);
if (version === 0) return send.deny();
if (version === 3) {
return send.object({
data: {
collabo: {
played: {
iidx: K.ITEM("s8", 1),
popn: K.ITEM("s8", 1),
ddr: K.ITEM("s8", 1),
reflec: K.ITEM("s8", 1),
gfdm: K.ITEM("s8", 1),
}
}
}
}
});
}
});
if (version === 4) {
return send.object({
data: {
player: {
collabo: {
reward: K.ITEM("s32", 0),
dellar: K.ITEM("s32", 0),
music_id: K.ITEM("s32", 0),
wonder_state: K.ITEM("u32", 2),
yellow_state: K.ITEM("u32", 2),
}
}
}
});
}
};

View File

@@ -1,6 +1,6 @@
import {demodata, netlog, shopinfo} from "./handlers/common";
import {demodata, gameInfo} from "./handlers/common";
import {check, entry, refresh, report} from "./handlers/matching";
import {getCollabo, loadScore, meeting, profile, saveProfile} from "./handlers/profile";
import {getCollabo, getCourse, loadScore, meeting, profile, saveProfile} from "./handlers/profile";
export function register() {
if (CORE_VERSION_MAJOR <= 1 && CORE_VERSION_MINOR < 31) {
@@ -8,41 +8,22 @@ export function register() {
return;
}
R.GameCode("J44");
R.Config("unlock_all_songs", {
name: "Unlock All Songs",
desc: "Tired of unlocking songs? Have this!",
type: "boolean",
default: false
});
R.Config("quick_matching_end", {
name: "Quick Matching End",
desc: "Supported from clan to festo.",
type: "boolean",
default: false
});
R.Config("matching_entry_timeout", {
name: "Online Matching Timeout",
desc: "If online matching songs are too boring, save time! (second)",
type: "integer",
default: 30,
range: [15, 99],
});
R.GameCode("K44");
R.GameCode("L44");
R.Route("gametop.regist", profile);
R.Route("gametop.get_info", gameInfo);
R.Route("gametop.get_pdata", profile);
R.Route("gametop.get_mdata", loadScore);
R.Route("gametop.get_course", getCourse);
R.Route("gametop.get_meeting", meeting);
R.Route("gametop.get_collabo", getCollabo);
R.Route('gameend.regist', saveProfile);
R.Route('gameend.log', true);
R.Route('gameend.set_collabo', true);
R.Route("gameend.regist", saveProfile);
R.Route("gameend.log", true);
R.Route("gameend.set_collabo", true);
R.Route("shopinfo.regist", shopinfo);
R.Route("netlog.send", netlog);
R.Route("shopinfo.regist", gameInfo);
R.Route("demodata.get_news", demodata.getNews);
R.Route("demodata.get_data", demodata.getData);
R.Route("demodata.get_hitchart", demodata.getHitchart);
@@ -51,10 +32,6 @@ export function register() {
R.Route("lobby.refresh", refresh);
R.Route("lobby.report", report);
R.Unhandled((info, data, send) => {
console.log(info.module, info.method);
console.log(U.toXML(data));
return send.deny();
});
R.Route("netlog.send", true);
R.Route("logger.report", true);
}

View File

@@ -0,0 +1,9 @@
export interface CourseResult {
collection: "course_results";
version: number;
courseId: number;
rating: number;
scores: number[];
}

View File

@@ -0,0 +1,18 @@
export interface Room {
collection: 'matching_rooms';
version: number;
roomId: number;
masterKey: number[];
masterGlobal: string;
masterPrivate: string;
localKey: number[];
musicId: number;
seqId: number;
members: {
cabid: number;
addr: string;
}[];
isFull: boolean;
isMatchEnd: boolean;
}

View File

@@ -1,101 +1,167 @@
export default class Profile {
collection: "profile" = "profile";
export default interface Profile {
collection: "profile";
jubeatId: number = _.random(1, 99999999);
name: string = "JUBEAT";
jubeatId: number;
name: string;
previous_version = 0;
lastPlayTime?: number;
lastShopname: string;
lastAreaname: string;
jubility: number = 0;
jubilityYday: number = 0;
tuneCount: number = 0;
saveCount: number = 0;
savedCount: number = 0;
fullcomboCount: number = 0;
fullcomboSeqCount: number = 0;
excellentCount: number = 0;
excellentSeqCount: number = 0;
matchCount: number = 0;
beatCount: number = 0;
tagCount: number = 0;
mynewsCount: number = 0;
conciergeSelectedCount: number = 0;
musicId?: number;
seqId?: number;
rankSort?: number;
comboDisp?: number;
last: {
shopname: string;
areaname: string;
playTime: bigint;
title: number;
theme: number;
marker: number;
showRank: number;
showCombo: number;
musicId: number;
seqId: number;
seqEditId: string;
sort: number;
filter: number;
mselStat: number;
conciergeSuggestId: number;
} = {
shopname: "NONE",
areaname: "NONE",
playTime: BigInt(0),
title: 0,
theme: 0,
marker: 0,
showRank: 1,
showCombo: 1,
musicId: 0,
seqId: 0,
seqEditId: "",
sort: 0,
filter: 0,
mselStat: 0,
conciergeSuggestId: 0
knit?: {
jubility?: number;
jubilityYday?: number;
acvProg?: number;
acvWool?: number;
acvRouteProg?: number[];
acvPoint?: number;
tuneCount?: number;
saveCount?: number;
savedCount?: number;
fcCount?: number;
fcSeqCount?: number;
exCount?: number;
exSeqCount?: number;
matchCount?: number;
conSelCount?: number;
marker?: number;
theme?: number;
title?: number;
sort?: number;
filter?: number;
mselStat?: number;
conSuggestId?: number;
secretList?: number[];
themeList?: number;
markerList?: number[];
titleList?: number[];
secretListNew?: number[];
themeListNew?: number;
markerListNew?: number[];
titleListNew?: number[];
};
knit: {
acvProg: number;
acvWool: number;
acvRouteProg: number[];
acvPoint: number;
item: {
secretList: number[],
themeList: number,
markerList: number[],
titleList: number[]
},
item_new: {
secretList: number[],
themeList: number,
markerList: number[],
titleList: number[]
},
collabo: {
success: boolean;
completed: boolean;
}
} = {
acvProg: 0,
acvWool: 0,
acvRouteProg: [0, 0, 0, 0],
acvPoint: 0,
item: {
secretList: [0, 0],
themeList: 0,
markerList: [0, 0],
titleList: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
item_new: {
secretList: [0, 0],
themeList: 0,
markerList: [0, 0],
titleList: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
collabo: {
success: false,
completed: false
copious?: {
jubility?: number;
jubilityYday?: number;
acvPoint?: number;
acvState?: number;
acvThrow?: number[];
acvOwn?: number;
tuneCount?: number;
saveCount?: number;
savedCount?: number;
fcCount?: number;
fcSeqCount?: number;
exCount?: number;
exSeqCount?: number;
matchCount?: number;
totalBestScore?: number;
marker?: number;
theme?: number;
title?: number;
parts?: number;
sort?: number;
category?: number;
mselStat?: number;
secretList?: number[];
themeList?: number;
markerList?: number[];
titleList?: number[];
partsList?: number[];
secretListNew?: number[];
themeListNew?: number;
markerListNew?: number[];
titleListNew?: number[];
};
saucer?: {
jubility?: number;
jubilityYday?: number;
tuneCount?: number;
clearCount?: number;
saveCount?: number;
savedCount?: number;
fcCount?: number;
exCount?: number;
matchCount?: number;
totalBestScore?: number;
marker?: number;
theme?: number;
title?: number;
parts?: number;
sort?: number;
category?: number;
secretList?: number[];
themeList?: number;
markerList?: number[];
titleList?: number[];
partsList?: number[];
secretListNew?: number[];
themeListNew?: number;
markerListNew?: number[];
titleListNew?: number[];
bistro?: {
carry_over?: number;
}
};
fulfill?: {
jubility?: number;
jubilityYday?: number;
tuneCount?: number;
clearCount?: number;
saveCount?: number;
savedCount?: number;
fcCount?: number;
exCount?: number;
matchCount?: number;
extraPoint?: number;
isExtraPlayed?: boolean;
totalBestScore?: number;
clearMaxLevel?: number;
fcMaxLevel?: number;
exMaxLevel?: number;
marker?: number;
theme?: number;
title?: number;
parts?: number;
sort?: number;
category?: number;
expertOption?: number;
matching?: number;
hazard?: number;
hard?: number;
secretList?: number[];
themeList?: number;
markerList?: number[];
titleList?: number[];
partsList?: number[];
secretListNew?: number[];
themeListNew?: number;
markerListNew?: number[];
titleListNew?: number[];
lastCourseId?: number;
};
}

View File

@@ -1,14 +1,15 @@
export class Score {
collection: "score" = "score";
export interface Score {
collection: "score";
musicId: number;
seq: number;
score: number = 0;
clearType: number = 0;
playCount: number = 0;
clearCount: number = 0;
fullcomboCount: number = 0;
excellentCount: number = 0;
isHardmodeClear: boolean;
score: number;
clear: number;
musicRate: number;
bar: number[];
playCount: number;
clearCount: number;
fullcomboCount: number;
excellentCount: number;
isHardMode: boolean;
}

View File

@@ -0,0 +1,28 @@
module.exports = () => ({
termver: K.ITEM("u8", 0),
season_etime: K.ITEM("u32", 0),
white_music_list: K.ARRAY("s32", Array(32).fill(-1)),
open_music_list: K.ARRAY("s32", Array(32).fill(0)),
collabo_info: {
collabo: [
K.ATTR({ type: "1" }, { state: K.ITEM("u8", 0) }),
K.ATTR({ type: "2" }, { state: K.ITEM("u8", 0) }),
K.ATTR({ type: "3" }, { state: K.ITEM("u8", 0) }),
K.ATTR({ type: "4" }, { state: K.ITEM("u8", 0) }),
K.ATTR({ type: "5" }, { state: K.ITEM("u8", 0) }),
K.ATTR({ type: "8" }, { state: K.ITEM("u8", 0) })
],
policy_break: {
is_report_end: K.ITEM("bool", true)
}
},
lab: {
is_open: K.ITEM("bool", false)
},
share_music: K.ATTR({ count: "0" }),
bonus_music: K.ATTR({ count: "0" })
});

View File

@@ -0,0 +1,27 @@
module.exports = () => ({
termver: K.ITEM("u8", 0),
season_etime: K.ITEM("u32", 0),
bistro_last_music_id: K.ITEM("s32", 0),
white_music_list: K.ARRAY("s32", Array(32).fill(-1)),
old_music_list: K.ARRAY("s32", Array(32).fill(0)),
open_music_list: K.ARRAY("s32", Array(32).fill(0)),
collabo_info: {
collabo: [
K.ATTR({ type: "1" }, { state: K.ITEM("u8", 0) }),
K.ATTR({ type: "2" }, { state: K.ITEM("u8", 0) }),
K.ATTR({ type: "3" }, { state: K.ITEM("u8", 0) }),
K.ATTR({ type: "4" }, { state: K.ITEM("u8", 0) }),
K.ATTR({ type: "5" }, { state: K.ITEM("u8", 0) }),
K.ATTR({ type: "8" }, { state: K.ITEM("u8", 0) })
],
run_run_marathon: {
is_report_end: K.ITEM("bool", true)
},
policy_break: {
is_report_end: K.ITEM("bool", true)
}
}
});

View File

@@ -1,89 +0,0 @@
gametop
data
player
name(__type="str") #{name}
jid(__type="s32") #{jubeatId}
refid(__type="str") #{refId}
session_id(__type="s32") 1
info
inherit(__type="bool") #{migration ? 1 : 0}
jubility(__type="s16") #{jubility}
jubility_yday(__type="s16") #{jubilityYday}
acv_prog(__type="s8") #{knit.acvProg}
acv_wool(__type="s8") #{knit.acvWool}
acv_route_prog(__type="s8" __count="4") #{knit.acvRouteProg.join(" ")}
acv_point(__type="s32") #{knit.acvPoint}
tune_cnt(__type="s32") #{tuneCount}
save_cnt(__type="s32") #{saveCount}
saved_cnt(__type="s32") #{savedCount}
fc_cnt(__type="s32") #{fullcomboCount}
ex_cnt(__type="s32") #{excellentCount}
match_cnt(__type="s32") #{matchCount}
beat_cnt(__type="s32") #{beatCount}
mynews_cnt(__type="s32") #{mynewsCount}
con_sel_cnt(__type="s32") #{conciergeSelectedCount}
tag_cnt(__type="s32") #{tagCount}
mtg_entry_cnt(__type="s32") 0
tag_entry_cnt(__type="s32") 0
mtg_hold_cnt(__type="s32") 0
tag_hold_cnt(__type="s32") 0
mtg_result(__type="u8") 0
last
play_time(__type="s64") #{last.playTime || 0}
shopname(__type="str") #{last.shopname}
areaname(__type="str") #{last.areaname}
title(__type="s16") #{last.title}
theme(__type="s8") #{last.theme}
marker(__type="s8") #{last.marker}
rank_sort(__type="s8") #{last.showRank}
combo_disp(__type="s8") #{last.showCombo}
music_id(__type="s32") #{last.musicId}
seq_id(__type="s8") #{last.seqId}
sort(__type="s8") #{last.sort}
filter(__type="s32") #{last.filter}
msel_stat(__type="s8") #{last.mselStat}
con_suggest_id(__type="s8") #{last.conciergeSuggestId}
item
secret_list(__type="s32" __count="2") #{knit.item.secretList.join(" ")}
theme_list(__type="s16") #{knit.item.themeList}
marker_list(__type="s32" __count="2") #{knit.item.markerList.join(" ")}
title_list(__type="s32" __count="24") #{knit.item.titleList.join(" ")}
new
secret_list(__type="s32" __count="2") #{knit.item_new.secretList.join(" ")}
theme_list(__type="s16") #{knit.item_new.themeList}
marker_list(__type="s32" __count="2") #{knit.item_new.markerList.join(" ")}
title_list(__type="s32" __count="24") #{knit.item_new.titleList.join(" ")}
today_music
music_id(__type="s32") 0
news
checked(__type="s16") 0
friendlist(count="0")
lucky_music
music_id(__type="s32") 0
mylist(count="0")
group
group_id(__type="s32") 0
bingo
reward
total(__type="s32") 0
point(__type="s32") 0
collabo
success(__type="bool") #{knit.collabo.success ? 1 : 0}
completed(__type="bool") #{knit.collabo.completed ? 1 : 0}
history
play_hist(count="0")
match_hist(count="0")

View File

@@ -0,0 +1,71 @@
import Profile from "../../models/profile";
module.exports = (data: Profile) => ({
info: {
jubility: K.ITEM("s16", data.copious?.jubility || 0),
jubility_yday: K.ITEM("s16", data.copious?.jubilityYday || 0),
acv_point: K.ITEM("s32", data.copious?.acvPoint || 0),
acv_state: K.ITEM("s8", data.copious?.acvState || 0),
acv_throw: K.ARRAY("s32", data.copious?.acvThrow || [0, 0, 0]),
acv_own: K.ITEM("s32", data.copious?.acvOwn || 0),
tune_cnt: K.ITEM("s32", data.copious?.tuneCount || 0),
save_cnt: K.ITEM("s32", data.copious?.saveCount || 0),
saved_cnt: K.ITEM("s32", data.copious?.savedCount || 0),
fc_cnt: K.ITEM("s32", data.copious?.fcCount || 0),
ex_cnt: K.ITEM("s32", data.copious?.exCount || 0),
match_cnt: K.ITEM("s32", data.copious?.matchCount || 0),
beat_cnt: K.ITEM("s32", 0),
mynews_cnt: K.ITEM("s32", 0),
mtg_entry_cnt: K.ITEM("s32", 0),
mtg_hold_cnt: K.ITEM("s32", 0),
mtg_result: K.ITEM("u8", 0)
},
last: {
play_time: K.ITEM("s64", BigInt(0)),
shopname: K.ITEM("str", data.lastShopname),
areaname: K.ITEM("str", data.lastAreaname),
title: K.ITEM("s16", data.copious?.title || 0),
parts: K.ITEM("s16", data.copious?.parts || 0),
theme: K.ITEM("s8", data.copious?.theme || 0),
marker: K.ITEM("s8", data.copious?.marker || 0),
rank_sort: K.ITEM("s8", data.rankSort || 1),
combo_disp: K.ITEM("s8", data.comboDisp || 1),
music_id: K.ITEM("s32", data.musicId || 0),
seq_id: K.ITEM("s8", data.seqId || 0),
sort: K.ITEM("s8", data.copious?.sort || 0),
category: K.ITEM("s8", data.copious?.category || 0),
msel_stat: K.ITEM("s8", data.copious?.mselStat || 0)
},
item: {
secret_list: K.ARRAY("s32", data.copious?.secretList || Array(12).fill(0)),
theme_list: K.ITEM("s16", data.copious?.themeList || 0),
marker_list: K.ARRAY("s32", data.copious?.markerList || [0, 0]),
title_list: K.ARRAY("s32", data.copious?.titleList || Array(32).fill(0)),
parts_list: K.ARRAY("s32", data.copious?.partsList || Array(96).fill(0)),
new: {
secret_list: K.ARRAY("s32", data.copious?.secretListNew || Array(12).fill(0)),
theme_list: K.ITEM("s16", data.copious?.themeListNew || 0),
marker_list: K.ARRAY("s32", data.copious?.markerListNew || [0, 0]),
title_list: K.ARRAY("s32", data.copious?.titleListNew || Array(32).fill(0))
}
},
challenge: {
today: {
music_id: K.ITEM("s32", 0)
},
onlynow: {
magic_no: K.ITEM("s32", 0),
cycle: K.ITEM("s16", 0)
}
},
news: {
checked: K.ITEM("s16", 0)
},
rivallist: K.ATTR({ count: "0" })
});

View File

@@ -0,0 +1,117 @@
import Profile from "../../models/profile";
module.exports = (data: Profile) => ({
info: {
jubility: K.ITEM("s16", data.fulfill?.jubility || 0),
jubility_yday: K.ITEM("s16", data.fulfill?.jubilityYday || 0),
tune_cnt: K.ITEM("s32", data.fulfill?.tuneCount || 31),
save_cnt: K.ITEM("s32", data.fulfill?.saveCount || 0),
saved_cnt: K.ITEM("s32", data.fulfill?.savedCount || 0),
fc_cnt: K.ITEM("s32", data.fulfill?.fcCount || 0),
ex_cnt: K.ITEM("s32", data.fulfill?.exCount || 0),
clear_cnt: K.ITEM("s32", data.fulfill?.clearCount || 0),
pf_cnt: K.ITEM("s32", 0),
match_cnt: K.ITEM("s32", data.fulfill?.matchCount || 0),
beat_cnt: K.ITEM("s32", 0),
mynews_cnt: K.ITEM("s32", 0),
mtg_entry_cnt: K.ITEM("s32", 0),
mtg_hold_cnt: K.ITEM("s32", 0),
mtg_result: K.ITEM("u8", 0),
extra_point: K.ITEM("s32", data.fulfill?.extraPoint || 0),
is_extra_played: K.ITEM("bool", data.fulfill?.isExtraPlayed || false)
},
last: {
play_time: K.ITEM("s64", BigInt(0)),
shopname: K.ITEM("str", data.lastShopname),
areaname: K.ITEM("str", data.lastAreaname),
title: K.ITEM("s16", data.fulfill?.title || 0),
parts: K.ITEM("s16", data.fulfill?.parts || 0),
theme: K.ITEM("s8", data.fulfill?.theme || 0),
marker: K.ITEM("s8", data.fulfill?.marker || 0),
rank_sort: K.ITEM("s8", data.rankSort || 1),
combo_disp: K.ITEM("s8", data.comboDisp || 1),
music_id: K.ITEM("s32", data.musicId || 0),
seq_id: K.ITEM("s8", data.seqId || 0),
sort: K.ITEM("s8", data.fulfill?.sort || 0),
category: K.ITEM("s8", data.fulfill?.category || 0),
expert_option: K.ITEM("s8", data.fulfill?.category || 0),
matching: K.ITEM("s8", data.fulfill?.category || 1),
hazard: K.ITEM("s8", data.fulfill?.category || 0),
hard: K.ITEM("s8", data.fulfill?.category || 0)
},
item: {
secret_list: K.ARRAY("s32", Array(32).fill(-1)),
theme_list: K.ITEM("s16", -1),
marker_list: K.ARRAY("s32", Array(2).fill(-1)),
title_list: K.ARRAY("s32", Array(96).fill(-1)),
parts_list: K.ARRAY("s32", Array(96).fill(-1)),
new: {
secret_list: K.ARRAY("s32", Array(32).fill(0)),
theme_list: K.ITEM("s16", 0),
marker_list: K.ARRAY("s32", Array(2).fill(0)),
title_list: K.ARRAY("s32", Array(96).fill(0))
}
},
history: K.ATTR({ count: "0" }),
challenge: {
today: {
music_id: K.ITEM("s32", 0),
state: K.ITEM("u8", 0)
}
},
news: {
checked: K.ITEM("s16", 0)
},
macchiato: {
pack_id: K.ITEM("s32", 0),
bean_num: K.ITEM("u16", 0),
daily_milk_num: K.ITEM("s32", 1200),
is_received_daily_milk: K.ITEM("bool", true),
today_tune_cnt: K.ITEM("s32", 0),
daily_milk_bonus: K.ARRAY("s32", [100, 100, 1000, 200, 200, 200, 200, 200, 1000]),
daily_play_burst: K.ITEM("s32", 300),
sub_menu_is_completed: K.ITEM("bool", true),
compensation_milk: K.ITEM("s32", 0),
macchiato_music_list: K.ATTR({ count: "0" }, {
music: []
}),
sub_pack_id: K.ITEM("s32", 0),
sub_macchiato_music_list: K.ATTR({ count: "0" }, {
music: []
}),
season_music_list: K.ATTR({ count: "0" }),
match_cnt: K.ITEM("s32", 0),
achievement_list: K.ATTR({ count: "0" }, {
achievement: []
}),
cow_list: K.ATTR({ count: "0" }),
},
rivallist: K.ATTR({ count: "0" }),
only_now_music: K.ATTR({ count: "0" }),
lab_edit_seq: K.ATTR({ count: "0" }),
kac_music: K.ATTR({ count: "0" }),
memorial: {
latest_event_id: K.ITEM("u8", 1),
player_event_id: K.ITEM("u8", 1),
flag: K.ITEM("u32", 0),
params: K.ARRAY("u32", Array(15).fill(0))
}
});

View File

@@ -0,0 +1,75 @@
import Profile from "../../models/profile";
module.exports = (data: Profile) => ({
info: {
jubility: K.ITEM("s16", data.knit?.jubility || 0),
jubility_yday: K.ITEM("s16", data.knit?.jubilityYday || 0),
acv_prog: K.ITEM("s8", data.knit?.acvProg || 0),
acv_wool: K.ITEM("s8", data.knit?.acvWool || 0),
acv_route_prog: K.ARRAY("s8", data.knit?.acvRouteProg || [0, 0, 0, 0]),
acv_point: K.ITEM("s32", data.knit?.acvPoint || 0),
tune_cnt: K.ITEM("s32", data.knit?.tuneCount || 0),
save_cnt: K.ITEM("s32", data.knit?.saveCount || 0),
saved_cnt: K.ITEM("s32", data.knit?.savedCount || 0),
fc_cnt: K.ITEM("s32", data.knit?.fcCount || 0),
ex_cnt: K.ITEM("s32", data.knit?.exCount || 0),
match_cnt: K.ITEM("s32", data.knit?.matchCount || 0),
beat_cnt: K.ITEM("s32", 0),
mynews_cnt: K.ITEM("s32", 0),
con_sel_cnt: K.ITEM("s32", data.knit?.conSelCount || 0),
tag_cnt: K.ITEM("s32", 0),
mtg_entry_cnt: K.ITEM("s32", 0),
tag_entry_cnt: K.ITEM("s32", 0),
mtg_hold_cnt: K.ITEM("s32", 0),
tag_hold_cnt: K.ITEM("s32", 0),
mtg_result: K.ITEM("u8", 0)
},
last: {
play_time: K.ITEM("s64", BigInt(0)),
shopname: K.ITEM("str", data.lastShopname),
areaname: K.ITEM("str", data.lastAreaname),
title: K.ITEM("s16", data.knit?.title || 0),
theme: K.ITEM("s8", data.knit?.theme || 0),
marker: K.ITEM("s8", data.knit?.marker || 0),
rank_sort: K.ITEM("s8", data.rankSort || 1),
combo_disp: K.ITEM("s8", data.comboDisp || 1),
music_id: K.ITEM("s32", data.musicId || 0),
seq_id: K.ITEM("s8", data.seqId || 0),
sort: K.ITEM("s8", data.knit?.sort || 0),
filter: K.ITEM("s32", data.knit?.filter || 0),
msel_stat: K.ITEM("s8", data.knit?.mselStat || 0),
con_suggest_id: K.ITEM("s8", data.knit?.conSuggestId || 0)
},
item: {
secret_list: K.ARRAY("s32", data.knit?.secretList || [0, 0]),
theme_list: K.ITEM("s16", data.knit?.themeList || 0),
marker_list: K.ARRAY("s32", data.knit?.markerList || [0, 0]),
title_list: K.ARRAY("s32", data.knit?.titleList || Array(24).fill(0)),
new: {
secret_list: K.ARRAY("s32", data.knit?.secretListNew || [0, 0]),
theme_list: K.ITEM("s16", data.knit?.themeListNew || 0),
marker_list: K.ARRAY("s32", data.knit?.markerListNew || [0, 0]),
title_list: K.ARRAY("s32", data.knit?.titleListNew || Array(24).fill(0))
}
},
today_music: {
music_id: K.ITEM("s32", 0)
},
news: {
checked: K.ITEM("s16", 0)
},
friendlist: K.ATTR({ count: "0" }),
mylist: K.ATTR({ count: "0" }),
collabo: {
success: K.ITEM("bool", true),
completed: K.ITEM("bool", true)
}
});

View File

@@ -0,0 +1,101 @@
import Profile from "../../models/profile";
module.exports = (data: Profile) => ({
info: {
jubility: K.ITEM("s16", data.saucer?.jubility || 0),
jubility_yday: K.ITEM("s16", data.saucer?.jubilityYday || 0),
tune_cnt: K.ITEM("s32", data.saucer?.tuneCount || 0),
save_cnt: K.ITEM("s32", data.saucer?.saveCount || 0),
saved_cnt: K.ITEM("s32", data.saucer?.savedCount || 0),
fc_cnt: K.ITEM("s32", data.saucer?.fcCount || 0),
ex_cnt: K.ITEM("s32", data.saucer?.exCount || 0),
clear_cnt: K.ITEM("s32", data.saucer?.clearCount || 0),
pf_cnt: K.ITEM("s32", 0),
match_cnt: K.ITEM("s32", data.saucer?.matchCount || 0),
beat_cnt: K.ITEM("s32", 0),
mynews_cnt: K.ITEM("s32", 0),
mtg_entry_cnt: K.ITEM("s32", 0),
mtg_hold_cnt: K.ITEM("s32", 0),
mtg_result: K.ITEM("u8", 0)
},
last: {
play_time: K.ITEM("s64", BigInt(0)),
shopname: K.ITEM("str", data.lastShopname),
areaname: K.ITEM("str", data.lastAreaname),
title: K.ITEM("s16", data.saucer?.title || 0),
parts: K.ITEM("s16", data.saucer?.parts || 0),
theme: K.ITEM("s8", data.saucer?.theme || 0),
marker: K.ITEM("s8", data.saucer?.marker || 0),
rank_sort: K.ITEM("s8", data.rankSort || 1),
combo_disp: K.ITEM("s8", data.comboDisp || 1),
music_id: K.ITEM("s32", data.musicId || 0),
seq_id: K.ITEM("s8", data.seqId || 0),
sort: K.ITEM("s8", data.saucer?.sort || 0),
category: K.ITEM("s8", data.saucer?.category || 0)
},
item: {
secret_list: K.ARRAY("s32", Array(32).fill(-1)),
theme_list: K.ITEM("s16", -1),
marker_list: K.ARRAY("s32", Array(2).fill(-1)),
title_list: K.ARRAY("s32", Array(96).fill(-1)),
parts_list: K.ARRAY("s32", Array(96).fill(-1)),
new: {
secret_list: K.ARRAY("s32", Array(32).fill(0)),
theme_list: K.ITEM("s16", 0),
marker_list: K.ARRAY("s32", Array(2).fill(0)),
title_list: K.ARRAY("s32", Array(96).fill(0))
}
},
history: K.ATTR({ count: "0" }),
challenge: {
today: {
music_id: K.ITEM("s32", 0),
state: K.ITEM("u8", 0)
}
},
news: {
checked: K.ITEM("s16", 0)
},
bistro: {
info: {
delicious_rate: K.ITEM("float", 1.0),
favorite_rate: K.ITEM("float", 1.0)
},
chef: {
id: K.ITEM("s32", 0),
ability: K.ITEM("u8", 0),
remain: K.ITEM("u8", 0),
rate: K.ARRAY("u8", [0, 0, 0, 0])
},
carry_over: K.ITEM("s32", data.saucer?.bistro?.carry_over || 0),
route: Array(9).fill(0).map((v, i) => (K.ATTR({ no: String(i) }, {
music: {
id: K.ITEM("s32", 0),
price_s32: K.ITEM("s32", 0)
},
gourmates: {
id: K.ITEM("s32", 0),
favorite: K.ARRAY("u8", Array(30).fill(0)),
satisfaction_s32: K.ITEM("s32", 0)
}
})))
},
rivallist: K.ATTR({ count: "0" }),
only_now_music: K.ATTR({ count: "0" }),
requested_music: K.ATTR({ count: "0" }),
lab_edit_seq: K.ATTR({ count: "0" }),
kac_music: K.ATTR({ count: "0" }),
});

View File

@@ -1,6 +1,30 @@
export function getVersion({ model }: EamuseInfo) {
const dateCode = model.split(':')[4];
const dateCode = parseInt(model.split(":")[4]);
if (model.startsWith("J44")) return 3;
if (model.startsWith("K44")) return 4;
if (model.startsWith("L44")) {
if (dateCode >= 2012082400 && dateCode <= 2014022400) return 5;
if (dateCode >= 2014030303 && dateCode <= 2014121802) return 6;
return 0;
}
return 0;
}
export function getVersionName({ model }: EamuseInfo) {
const dateCode = parseInt(model.split(":")[4]);
if (model.startsWith("J44")) return "knit";
if (model.startsWith("K44")) return "copious";
if (model.startsWith("L44")) {
if (dateCode >= 2012082400 && dateCode <= 2014022400) return "saucer";
if (dateCode >= 2014030303 && dateCode <= 2014121802) return "fulfill";
return null;
}
return null;
}
export function VersionRange(version: number, start: number, end: number = -1) {
if (end === -1) return version >= start;
return version >= start && version <= end;
}

View File

@@ -7,5 +7,5 @@ export function isRequiredVersion(major: number, minor: number) {
// version value exposed since Core v1.19
const core_major = typeof CORE_VERSION_MAJOR === "number" ? CORE_VERSION_MAJOR : 1
const core_minor = typeof CORE_VERSION_MINOR === "number" ? CORE_VERSION_MINOR : 18
return core_major >= major && core_minor >= minor
return core_major > major || (core_major === major && core_minor >= minor)
}