mirror of
https://github.com/djhackersdev/minime.git
synced 2026-04-22 09:17:28 -05:00
Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87661cf871 | ||
|
|
d6d98aa561 | ||
|
|
2c78e6646a | ||
|
|
0f8010ae8d | ||
|
|
cb5db5e55f | ||
|
|
a99503a86d | ||
|
|
37660a382a | ||
|
|
e43faeff10 | ||
|
|
2189132753 | ||
|
|
85aad3377e | ||
|
|
2f5a63858f | ||
|
|
dbffb15779 | ||
|
|
1f544ddb7e | ||
|
|
7f47315130 | ||
|
|
afe9f27c25 | ||
|
|
f8ca0bdcd3 | ||
|
|
98caa9248b | ||
|
|
0236d44466 | ||
|
|
4c0e933ea4 | ||
|
|
8b284107b1 | ||
|
|
2c31bd0c47 | ||
|
|
7b329bc37c | ||
|
|
0937c0fadf | ||
|
|
ea92e4e981 | ||
|
|
1cfd577f56 | ||
|
|
b7187fe0ed | ||
|
|
60c089a810 | ||
|
|
377096a077 | ||
|
|
87de79ef12 | ||
|
|
f1386f8e3d | ||
|
|
6d581c114d | ||
|
|
9570349565 | ||
|
|
206d12742a | ||
|
|
f2434cef7e | ||
|
|
2474879c89 | ||
|
|
4c36e0abf0 | ||
|
|
ccd5fd72c8 | ||
|
|
46499a5425 | ||
|
|
8562da15b6 | ||
|
|
8195e9f937 | ||
|
|
9f57ff1342 | ||
|
|
a7b7a01685 | ||
|
|
8ada783f7c | ||
|
|
f79c4a4370 | ||
|
|
f3be39823a | ||
|
|
03d0e87b55 | ||
|
|
efcc1caa76 | ||
|
|
2a546c0d9d | ||
|
|
63627eaf78 | ||
|
|
ed7c461fe1 | ||
|
|
75ac6e03f2 | ||
|
|
2f2dc82b5a | ||
|
|
198256e6ff | ||
|
|
eb276128c2 | ||
|
|
6090a60467 | ||
|
|
2099622b0f | ||
|
|
9494f3026b | ||
|
|
f7ceb6bcda | ||
|
|
04dac7279e | ||
|
|
8c164b6130 | ||
|
|
27b229e1c2 | ||
|
|
c877d9d214 | ||
|
|
636789b8bf | ||
|
|
48aeb1c9d2 | ||
|
|
5f09c00eb4 | ||
|
|
bf28710000 | ||
|
|
8c61bda865 | ||
|
|
1fa268e6ba | ||
|
|
4fe69077fc | ||
|
|
4f5eb97309 | ||
|
|
122c397b59 | ||
|
|
d3984dcd5d | ||
|
|
4abc04e9f6 | ||
|
|
c01dbb50f7 | ||
|
|
fa5ed284e1 | ||
|
|
76583f17ce | ||
|
|
f959993197 | ||
|
|
6dceb7c48a | ||
|
|
1869c37949 | ||
|
|
1d8fd582c9 | ||
|
|
9b22318934 | ||
|
|
71a7cd7eed | ||
|
|
6c22c4287e | ||
|
|
a8539b475e | ||
|
|
abcccfb9a0 | ||
|
|
b2288b9b8f | ||
|
|
e61b103221 | ||
|
|
94ed27503a | ||
|
|
ad1b48c210 | ||
|
|
75b35e09e2 | ||
|
|
834804b2ea | ||
|
|
ac9cf50d28 | ||
|
|
cb4743f86f | ||
|
|
cef7e99e46 | ||
|
|
4b007b446d | ||
|
|
4c5122d163 | ||
|
|
01198d546a | ||
|
|
bc5f26d768 | ||
|
|
9a652e1ace | ||
|
|
7b21a0bdae | ||
|
|
5311618d1d | ||
|
|
37ce14dda7 | ||
|
|
0d8d5845c9 | ||
|
|
143f534ba1 | ||
|
|
4db3d94ee8 | ||
|
|
e195cf77cd | ||
|
|
ac9622f1bd | ||
|
|
e8535b1a80 |
16
.gitlab-ci.yml
Normal file
16
.gitlab-ci.yml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
image: node:12-buster
|
||||||
|
|
||||||
|
cache:
|
||||||
|
paths:
|
||||||
|
- node_modules/
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
|
||||||
|
do_build:
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- npm install
|
||||||
|
- npm run build
|
||||||
|
- npm test
|
||||||
|
|
||||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
|
@ -1,5 +1,8 @@
|
||||||
{
|
{
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.rulers": [79],
|
"editor.rulers": [79],
|
||||||
"typescript.tsdk": "node_modules/typescript/lib"
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
70
CHANGELOG.md
Normal file
70
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v016
|
||||||
|
|
||||||
|
- IDZ: Fix SQL syntax error (Tau)
|
||||||
|
|
||||||
|
## v015
|
||||||
|
|
||||||
|
- IDZ: Team fixes (BemaniWitch)
|
||||||
|
|
||||||
|
## v014
|
||||||
|
|
||||||
|
- IDZ: Add support for Initial D v2.1 (BemaniWitch)
|
||||||
|
|
||||||
|
## v013
|
||||||
|
|
||||||
|
- IDZ: Miscellaneous refactoring (Tau)
|
||||||
|
- Chunithm: Add stubs to support Amazon Plus (Felix)
|
||||||
|
|
||||||
|
## v012
|
||||||
|
|
||||||
|
- Chunithm: Fix large data loads (NeumPhis)
|
||||||
|
- Chunithm: Add some support for older versions (NeumPhis)
|
||||||
|
- Chunithm: Fix large data loads (Felix)
|
||||||
|
- Chunithm: Enable all events (Felix)
|
||||||
|
- Chunithm: Remove server maintenance period (Felix)
|
||||||
|
- Chunithm: Fix data loss (esterTion)
|
||||||
|
- Chunithm: Add some support for older versions (esterTion)
|
||||||
|
- IDZ: Team-related fixes (Emi Midnight)
|
||||||
|
- Add support for newer AIME card services (cxm)
|
||||||
|
- Chunithm: Enable ticket system (Rob)
|
||||||
|
- Chunithm: Implement Recent Rating feature (seika1)
|
||||||
|
- Chunithm: Fix User Activity feature (seika1)
|
||||||
|
|
||||||
|
## v011
|
||||||
|
|
||||||
|
- IDZ: Fix loading of >10 cars in garage (Tau)
|
||||||
|
|
||||||
|
## v010
|
||||||
|
|
||||||
|
- Chunithm: Bug fix: Fix new database creation and repair affected databases. Again. (Tau)
|
||||||
|
Switch to a different SQLite native code extension. C compiler is no longer required. (Tau)
|
||||||
|
|
||||||
|
## v009
|
||||||
|
|
||||||
|
- IDZ: Display team banners in attract loop rankings (Tau)
|
||||||
|
- Chunithm: Bug fix: Fix new database creation and repair affected databases (Tau)
|
||||||
|
|
||||||
|
## v008
|
||||||
|
|
||||||
|
- Implement auras in IDZ (BemaniWitch)
|
||||||
|
- Fix user ratings (NeumPhis)
|
||||||
|
- Load and save course results (NeumPhis)
|
||||||
|
- Load and save user duel list (Felix)
|
||||||
|
|
||||||
|
## v007
|
||||||
|
|
||||||
|
- Fix IDZ team creation (Tau)
|
||||||
|
- Fix startup script working directory (Tau)
|
||||||
|
|
||||||
|
## v006
|
||||||
|
|
||||||
|
- Fix Chunithm HTTP POST size limit (Tau)
|
||||||
|
|
||||||
|
## v005
|
||||||
|
|
||||||
|
- Initial public release (Tau)
|
||||||
|
- Chunithm v1.30.00 Amazon
|
||||||
|
- Initial D Zero v1.21.00
|
||||||
|
- Initial D Zero v1.31.00
|
||||||
5921
package-lock.json
generated
5921
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
|
|
@ -5,7 +5,7 @@
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-sqlite3": "^5.4.3",
|
"@decafcode/sqlite": "^2.0.0",
|
||||||
"compression": "^1.7.3",
|
"compression": "^1.7.3",
|
||||||
"date-fns": "^1.30.1",
|
"date-fns": "^1.30.1",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
|
|
@ -20,7 +20,6 @@
|
||||||
"supports-color": "^7.1.0"
|
"supports-color": "^7.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^5.4.0",
|
|
||||||
"@types/compression": "^0.0.36",
|
"@types/compression": "^0.0.36",
|
||||||
"@types/debug": "^4.1.5",
|
"@types/debug": "^4.1.5",
|
||||||
"@types/dotenv": "^6.1.1",
|
"@types/dotenv": "^6.1.1",
|
||||||
|
|
@ -28,12 +27,12 @@
|
||||||
"@types/jest": "^24.0.11",
|
"@types/jest": "^24.0.11",
|
||||||
"@types/multiparty": "^0.0.32",
|
"@types/multiparty": "^0.0.32",
|
||||||
"@types/node": "^12.11.7",
|
"@types/node": "^12.11.7",
|
||||||
"jest": "^24.5.0",
|
"jest": "^26.4.2",
|
||||||
"jest-haste-map": "^24.5.0",
|
"jest-haste-map": "^26.3.0",
|
||||||
"jest-resolve": "^24.5.0",
|
"jest-resolve": "^24.5.0",
|
||||||
"prettier": "^1.16.4",
|
"prettier": "^1.16.4",
|
||||||
"ts-jest": "^24.0.0",
|
"ts-jest": "^26.3.0",
|
||||||
"typescript": "^3.6.4",
|
"typescript": "^3.8.3",
|
||||||
"utility-types": "^3.6.1"
|
"utility-types": "^3.6.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,44 @@ create table "cm_user_activity" (
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table "cm_user_charge" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "cm_user_data"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"charge_id" integer not null,
|
||||||
|
"stock" integer not null,
|
||||||
|
"purchase_date" text not null,
|
||||||
|
"valid_date" text not null,
|
||||||
|
"param1" integer not null,
|
||||||
|
"param2" integer not null,
|
||||||
|
"param_date" text not null,
|
||||||
|
constraint "cm_user_charge_uq" unique ("profile_id", "charge_id")
|
||||||
|
);
|
||||||
|
|
||||||
|
create table "cm_user_course" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "cm_user_data"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"course_id" integer not null,
|
||||||
|
"class_id" integer not null,
|
||||||
|
"play_count" integer not null,
|
||||||
|
"score_max" integer not null,
|
||||||
|
"is_full_combo" text not null,
|
||||||
|
"is_all_justice" text not null,
|
||||||
|
"is_success" text not null,
|
||||||
|
"score_rank" integer not null,
|
||||||
|
"event_id" integer not null,
|
||||||
|
"last_play_date" text not null,
|
||||||
|
"param1" integer not null,
|
||||||
|
"param2" integer not null,
|
||||||
|
"param3" integer not null,
|
||||||
|
"param4" integer not null,
|
||||||
|
"is_clear" text not null,
|
||||||
|
constraint "cm_user_course_uq" unique ("profile_id", "course_id")
|
||||||
|
);
|
||||||
|
|
||||||
create table "cm_user_data_ex" (
|
create table "cm_user_data_ex" (
|
||||||
"id" integer primary key not null
|
"id" integer primary key not null
|
||||||
references "cm_user_data"("id")
|
references "cm_user_data"("id")
|
||||||
|
|
@ -122,6 +160,23 @@ create table "cm_user_data_ex" (
|
||||||
"ext_long5" integer not null
|
"ext_long5" integer not null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table "cm_user_duel_list" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "cm_user_data"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"duel_id" integer not null,
|
||||||
|
"progress" integer not null,
|
||||||
|
"point" integer not null,
|
||||||
|
"is_clear" boolean not null,
|
||||||
|
"last_play_date" text not null,
|
||||||
|
"param1" integer not null,
|
||||||
|
"param2" integer not null,
|
||||||
|
"param3" integer not null,
|
||||||
|
"param4" integer not null,
|
||||||
|
constraint "cm_user_duel_list_uq" unique ("profile_id", "duel_id")
|
||||||
|
);
|
||||||
|
|
||||||
create table "cm_user_character" (
|
create table "cm_user_character" (
|
||||||
"id" integer primary key not null,
|
"id" integer primary key not null,
|
||||||
"profile_id" integer not null
|
"profile_id" integer not null
|
||||||
|
|
@ -302,3 +357,16 @@ create table "cm_user_playlog" (
|
||||||
"place_name" text not null,
|
"place_name" text not null,
|
||||||
"is_maimai" text not null
|
"is_maimai" text not null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table "cm_user_recent_rating" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "cm_user_data"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"sort_order" integer not null,
|
||||||
|
"music_id" integer not null,
|
||||||
|
"difficult_id" integer not null,
|
||||||
|
"rom_version_code" integer not null,
|
||||||
|
"score" integer not null,
|
||||||
|
constraint "cm_user_recent_rating_uq" unique ("profile_id", "sort_order")
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ create table "idz_profile" (
|
||||||
"player_id" integer not null
|
"player_id" integer not null
|
||||||
references "aime_player"("id")
|
references "aime_player"("id")
|
||||||
on delete cascade,
|
on delete cascade,
|
||||||
|
-- Major version of Initial D Zero, either 1 or 2.
|
||||||
|
-- The two major versions are incompatible with each other and do not
|
||||||
|
-- permit the player to carry progress over from one to the other.
|
||||||
|
"version" integer not null,
|
||||||
-- TODO shop_id
|
-- TODO shop_id
|
||||||
"name" text not null,
|
"name" text not null,
|
||||||
"lv" integer not null,
|
"lv" integer not null,
|
||||||
|
|
@ -16,7 +20,7 @@ create table "idz_profile" (
|
||||||
"mileage" integer not null,
|
"mileage" integer not null,
|
||||||
"register_time" timestamp not null,
|
"register_time" timestamp not null,
|
||||||
"access_time" timestamp not null,
|
"access_time" timestamp not null,
|
||||||
constraint "idz_profile_player_uq" unique ("player_id")
|
constraint "idz_profile_player_uq" unique ("player_id", "version")
|
||||||
);
|
);
|
||||||
|
|
||||||
create table "idz_chara" (
|
create table "idz_chara" (
|
||||||
|
|
@ -100,7 +104,27 @@ create table "idz_settings" (
|
||||||
"pack" integer not null,
|
"pack" integer not null,
|
||||||
"aura" integer not null,
|
"aura" integer not null,
|
||||||
"paper_cup" integer not null, -- Not a boolean, oddly enough
|
"paper_cup" integer not null, -- Not a boolean, oddly enough
|
||||||
"gauges" integer not null
|
"gauges" integer not null,
|
||||||
|
"driving_style" integer not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table "idz_stamp_selections" (
|
||||||
|
"id" integer primary key not null
|
||||||
|
references "idz_profile"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"stamp_01" integer not null,
|
||||||
|
"stamp_02" integer not null,
|
||||||
|
"stamp_03" integer not null,
|
||||||
|
"stamp_04" integer not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table "idz_stamp_unlock" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "idz_profile"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"stamp_no",
|
||||||
|
constraint "idz_stamp_unlock_uq" unique ("profile_id", "stamp_no")
|
||||||
);
|
);
|
||||||
|
|
||||||
create table "idz_story_state" (
|
create table "idz_story_state" (
|
||||||
|
|
@ -120,6 +144,7 @@ create table "idz_story_cell_state" (
|
||||||
"col_no" integer not null,
|
"col_no" integer not null,
|
||||||
"a" integer not null,
|
"a" integer not null,
|
||||||
"b" integer not null,
|
"b" integer not null,
|
||||||
|
"c" integer not null,
|
||||||
constraint "idz_story_cell_state_uq" unique (
|
constraint "idz_story_cell_state_uq" unique (
|
||||||
"profile_id",
|
"profile_id",
|
||||||
"row_no",
|
"row_no",
|
||||||
|
|
@ -202,12 +227,13 @@ create table "idz_unlocks" (
|
||||||
|
|
||||||
create table "idz_team" (
|
create table "idz_team" (
|
||||||
"id" integer primary key not null,
|
"id" integer primary key not null,
|
||||||
|
"version" integer not null,
|
||||||
"ext_id" integer not null,
|
"ext_id" integer not null,
|
||||||
"name" text not null,
|
"name" text not null,
|
||||||
"name_bg" integer not null,
|
"name_bg" integer not null,
|
||||||
"name_fx" integer not null,
|
"name_fx" integer not null,
|
||||||
"register_time" timestamp not null,
|
"register_time" timestamp not null,
|
||||||
constraint "idz_team_uq" unique ("ext_id")
|
constraint "idz_team_uq" unique ("version", "ext_id")
|
||||||
);
|
);
|
||||||
|
|
||||||
create table "idz_team_auto" (
|
create table "idz_team_auto" (
|
||||||
|
|
@ -215,8 +241,7 @@ create table "idz_team_auto" (
|
||||||
references "idz_team"("id")
|
references "idz_team"("id")
|
||||||
on delete cascade,
|
on delete cascade,
|
||||||
"serial_no" integer not null,
|
"serial_no" integer not null,
|
||||||
"name_idx" integer not null,
|
"name_idx" integer not null
|
||||||
constraint "idz_team_auto_uq" unique ("serial_no", "name_idx")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
create table "idz_team_member" (
|
create table "idz_team_member" (
|
||||||
|
|
@ -240,3 +265,25 @@ create table "idz_team_reservation" (
|
||||||
"join_time" timestamp not null,
|
"join_time" timestamp not null,
|
||||||
"leader" boolean not null
|
"leader" boolean not null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table "idz_weekly_missions" (
|
||||||
|
"id" integer primary key not null
|
||||||
|
references "idz_profile"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"weekly_reset" timestamp not null,
|
||||||
|
"mission_left" integer not null,
|
||||||
|
"progress_left" integer not null,
|
||||||
|
"params_left" integer not null,
|
||||||
|
"mission_right" integer not null,
|
||||||
|
"progress_right" integer not null,
|
||||||
|
"params_right" integer not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table "idz_my_chara" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "idz_profile"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"my_chara_no" integer not null,
|
||||||
|
constraint "idz_my_chara_uq" unique ("profile_id", "my_chara_no")
|
||||||
|
);
|
||||||
|
|
|
||||||
23
schema/migrate/M0012-cm-user-course-fix.sql
Normal file
23
schema/migrate/M0012-cm-user-course-fix.sql
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
-- This was not present in new DBs initialized to schema version 11.
|
||||||
|
create table if not exists "cm_user_course" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "cm_user_data"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"course_id" integer not null,
|
||||||
|
"class_id" integer not null,
|
||||||
|
"play_count" integer not null,
|
||||||
|
"score_max" integer not null,
|
||||||
|
"is_full_combo" text not null,
|
||||||
|
"is_all_justice" text not null,
|
||||||
|
"is_success" text not null,
|
||||||
|
"score_rank" integer not null,
|
||||||
|
"event_id" integer not null,
|
||||||
|
"last_play_date" text not null,
|
||||||
|
"param1" integer not null,
|
||||||
|
"param2" integer not null,
|
||||||
|
"param3" integer not null,
|
||||||
|
"param4" integer not null,
|
||||||
|
"is_clear" text not null,
|
||||||
|
constraint "cm_user_course_uq" unique ("profile_id", "course_id")
|
||||||
|
);
|
||||||
17
schema/migrate/M0013-cm-user-duel-list-fix.sql
Normal file
17
schema/migrate/M0013-cm-user-duel-list-fix.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
-- This was also not present in the db init scripts...
|
||||||
|
create table if not exists "cm_user_duel_list" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "cm_user_data"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"duel_id" integer not null,
|
||||||
|
"progress" integer not null,
|
||||||
|
"point" integer not null,
|
||||||
|
"is_clear" boolean not null,
|
||||||
|
"last_play_date" text not null,
|
||||||
|
"param1" integer not null,
|
||||||
|
"param2" integer not null,
|
||||||
|
"param3" integer not null,
|
||||||
|
"param4" integer not null,
|
||||||
|
constraint "cm_user_duel_list_uq" unique ("profile_id", "duel_id")
|
||||||
|
);
|
||||||
14
schema/migrate/M0014-cm-user-charge.sql
Normal file
14
schema/migrate/M0014-cm-user-charge.sql
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
create table if not exists "cm_user_charge" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "cm_user_data"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"charge_id" integer not null,
|
||||||
|
"stock" integer not null,
|
||||||
|
"purchase_date" text not null,
|
||||||
|
"valid_date" text not null,
|
||||||
|
"param1" integer not null,
|
||||||
|
"param2" integer not null,
|
||||||
|
"param_date" text not null,
|
||||||
|
constraint "cm_user_charge_uq" unique ("profile_id", "charge_id")
|
||||||
|
);
|
||||||
31
schema/migrate/M0015-cm-user-recent-rating.sql
Normal file
31
schema/migrate/M0015-cm-user-recent-rating.sql
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
create table if not exists "cm_user_recent_rating" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "cm_user_data"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"sort_order" integer not null,
|
||||||
|
"music_id" integer not null,
|
||||||
|
"difficult_id" integer not null,
|
||||||
|
"rom_version_code" integer not null,
|
||||||
|
"score" integer not null,
|
||||||
|
constraint "cm_user_recent_rating_uq" unique ("profile_id", "sort_order")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Prepopulate this table by backfilling the most recent 30 scores per user.
|
||||||
|
-- This isn't exactly correct since not every recent score should go in here, but it's probably close enough.
|
||||||
|
INSERT INTO cm_user_recent_rating (profile_id, sort_order, music_id, difficult_id, rom_version_code, score)
|
||||||
|
SELECT * FROM (
|
||||||
|
SELECT
|
||||||
|
profile_id,
|
||||||
|
row_number()
|
||||||
|
OVER (PARTITION BY profile_id ORDER BY user_play_date DESC) AS sort_order,
|
||||||
|
music_id,
|
||||||
|
level AS difficult_id,
|
||||||
|
'1030000' AS rom_version_code,
|
||||||
|
score
|
||||||
|
FROM cm_user_playlog
|
||||||
|
WHERE
|
||||||
|
difficult_id < 4 -- skip world's end
|
||||||
|
ORDER BY profile_id ASC, user_play_date DESC
|
||||||
|
)
|
||||||
|
WHERE sort_order <= 30;
|
||||||
77
schema/migrate/M0016-idz-profile-version.sql
Normal file
77
schema/migrate/M0016-idz-profile-version.sql
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
create table "new_idz_profile" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"player_id" integer not null
|
||||||
|
references "aime_player"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"version" integer not null,
|
||||||
|
"name" text not null,
|
||||||
|
"lv" integer not null,
|
||||||
|
"exp" integer not null,
|
||||||
|
"fame" integer not null,
|
||||||
|
"dpoint" integer not null,
|
||||||
|
"mileage" integer not null,
|
||||||
|
"register_time" timestamp not null,
|
||||||
|
"access_time" timestamp not null,
|
||||||
|
constraint "idz_profile_player_uq" unique ("player_id", "version")
|
||||||
|
);
|
||||||
|
|
||||||
|
create table "new_idz_team" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"version" integer not null,
|
||||||
|
"ext_id" integer not null,
|
||||||
|
"name" text not null,
|
||||||
|
"name_bg" integer not null,
|
||||||
|
"name_fx" integer not null,
|
||||||
|
"register_time" timestamp not null,
|
||||||
|
constraint "idz_team_uq" unique ("version", "ext_id")
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into "new_idz_profile" (
|
||||||
|
"id",
|
||||||
|
"player_id",
|
||||||
|
"version",
|
||||||
|
"name",
|
||||||
|
"lv",
|
||||||
|
"exp",
|
||||||
|
"fame",
|
||||||
|
"dpoint",
|
||||||
|
"mileage",
|
||||||
|
"register_time",
|
||||||
|
"access_time"
|
||||||
|
) select
|
||||||
|
x."id",
|
||||||
|
x."player_id",
|
||||||
|
1,
|
||||||
|
x."name",
|
||||||
|
x."lv",
|
||||||
|
x."exp",
|
||||||
|
x."fame",
|
||||||
|
x."dpoint",
|
||||||
|
x."mileage",
|
||||||
|
x."register_time",
|
||||||
|
x."access_time"
|
||||||
|
from "idz_profile" as "x";
|
||||||
|
|
||||||
|
insert into "new_idz_team" (
|
||||||
|
"id",
|
||||||
|
"version",
|
||||||
|
"ext_id",
|
||||||
|
"name",
|
||||||
|
"name_bg",
|
||||||
|
"name_fx",
|
||||||
|
"register_time"
|
||||||
|
) select
|
||||||
|
x."id",
|
||||||
|
1,
|
||||||
|
x."ext_id",
|
||||||
|
x."name",
|
||||||
|
x."name_bg",
|
||||||
|
x."name_fx",
|
||||||
|
x."register_time"
|
||||||
|
from "idz_team" as "x";
|
||||||
|
|
||||||
|
drop table "idz_profile";
|
||||||
|
drop table "idz_team";
|
||||||
|
|
||||||
|
alter table "new_idz_profile" rename to "idz_profile";
|
||||||
|
alter table "new_idz_team" rename to "idz_team";
|
||||||
111
schema/migrate/M0017-idz-v2-support.sql
Normal file
111
schema/migrate/M0017-idz-v2-support.sql
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
create table "idz_my_chara" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "idz_profile"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"my_chara_no" integer not null,
|
||||||
|
constraint "idz_my_chara_uq" unique ("profile_id", "my_chara_no")
|
||||||
|
);
|
||||||
|
|
||||||
|
create table "new_idz_settings" (
|
||||||
|
"id" integer primary key not null
|
||||||
|
references "idz_profile"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"music" integer not null,
|
||||||
|
"pack" integer not null,
|
||||||
|
"aura" integer not null,
|
||||||
|
"paper_cup" integer not null, -- Not a boolean, oddly enough
|
||||||
|
"gauges" integer not null,
|
||||||
|
"driving_style" integer not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table "idz_stamp_selections" (
|
||||||
|
"id" integer primary key not null
|
||||||
|
references "idz_profile"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"stamp_01" integer not null,
|
||||||
|
"stamp_02" integer not null,
|
||||||
|
"stamp_03" integer not null,
|
||||||
|
"stamp_04" integer not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table "idz_stamp_unlock" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "idz_profile"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"stamp_no",
|
||||||
|
constraint "idz_stamp_unlock_uq" unique ("profile_id", "stamp_no")
|
||||||
|
);
|
||||||
|
|
||||||
|
create table "new_idz_story_cell_state" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "idz_profile"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"row_no" integer not null,
|
||||||
|
"col_no" integer not null,
|
||||||
|
"a" integer not null,
|
||||||
|
"b" integer not null,
|
||||||
|
"c" integer not null,
|
||||||
|
constraint "idz_story_cell_state_uq" unique (
|
||||||
|
"profile_id",
|
||||||
|
"row_no",
|
||||||
|
"col_no"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table "idz_weekly_missions" (
|
||||||
|
"id" integer primary key not null
|
||||||
|
references "idz_profile"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"weekly_reset" timestamp not null,
|
||||||
|
"mission_left" integer not null,
|
||||||
|
"progress_left" integer not null,
|
||||||
|
"params_left" integer not null,
|
||||||
|
"mission_right" integer not null,
|
||||||
|
"progress_right" integer not null,
|
||||||
|
"params_right" integer not null
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into "new_idz_settings" (
|
||||||
|
"id",
|
||||||
|
"music",
|
||||||
|
"pack",
|
||||||
|
"aura",
|
||||||
|
"paper_cup",
|
||||||
|
"gauges",
|
||||||
|
"driving_style"
|
||||||
|
) select
|
||||||
|
x."id",
|
||||||
|
x."music",
|
||||||
|
x."pack",
|
||||||
|
x."aura",
|
||||||
|
x."paper_cup",
|
||||||
|
x."gauges",
|
||||||
|
0
|
||||||
|
from "idz_settings" as x;
|
||||||
|
|
||||||
|
insert into "new_idz_story_cell_state" (
|
||||||
|
"id",
|
||||||
|
"profile_id",
|
||||||
|
"row_no",
|
||||||
|
"col_no",
|
||||||
|
"a",
|
||||||
|
"b",
|
||||||
|
"c"
|
||||||
|
) select
|
||||||
|
x."id",
|
||||||
|
x."profile_id",
|
||||||
|
x."row_no",
|
||||||
|
x."col_no",
|
||||||
|
x."a",
|
||||||
|
x."b",
|
||||||
|
0
|
||||||
|
from "idz_story_cell_state" as x;
|
||||||
|
|
||||||
|
drop table "idz_settings";
|
||||||
|
drop table "idz_story_cell_state";
|
||||||
|
|
||||||
|
alter table "new_idz_settings" rename to "idz_settings";
|
||||||
|
alter table "new_idz_story_cell_state" rename to "idz_story_cell_state";
|
||||||
13
schema/migrate/M0018-idz-auto-team-fix.sql
Normal file
13
schema/migrate/M0018-idz-auto-team-fix.sql
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
create table "new_idz_team_auto" (
|
||||||
|
"id" integer primary key not null
|
||||||
|
references "idz_team"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"serial_no" integer not null,
|
||||||
|
"name_idx" integer not null
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into "new_idz_team_auto"
|
||||||
|
select "id", "serial_no", "name_idx" from "idz_team_auto";
|
||||||
|
|
||||||
|
drop table "idz_team_auto";
|
||||||
|
alter table "new_idz_team_auto" rename to "idz_team_auto";
|
||||||
|
|
@ -105,7 +105,7 @@ test("decode lookup", () => {
|
||||||
|
|
||||||
const obj = decode(req);
|
const obj = decode(req);
|
||||||
|
|
||||||
expect(obj.type).toBe("lookup");
|
expect(obj.type).toBe("lookup2");
|
||||||
expect(obj.gameId).toBe("SDBT");
|
expect(obj.gameId).toBe("SDBT");
|
||||||
expect(obj.keychipId).toBe("A69E01A9999");
|
expect(obj.keychipId).toBe("A69E01A9999");
|
||||||
expect(obj.luid).toBe("01036495850523030676");
|
expect(obj.luid).toBe("01036495850523030676");
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,15 @@ function readFeliCaLookupRequest(msg: Buffer): Request.FeliCaLookupRequest {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readFeliCaLookupRequest2(msg: Buffer): Request.FeliCaLookup2Request {
|
||||||
|
return {
|
||||||
|
...begin(msg),
|
||||||
|
type: "felica_lookup2",
|
||||||
|
idm: msg.slice(0x0030, 0x0038).toString("hex"),
|
||||||
|
pmm: msg.slice(0x0038, 0x0040).toString("hex"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function readLogRequest(msg: Buffer): Request.LogRequest {
|
function readLogRequest(msg: Buffer): Request.LogRequest {
|
||||||
// idk what any of this stuff means yet
|
// idk what any of this stuff means yet
|
||||||
// field20 and field28 appear to be an aime id but that is all.
|
// field20 and field28 appear to be an aime id but that is all.
|
||||||
|
|
@ -98,6 +107,7 @@ readers.set(0x0009, readLogRequest);
|
||||||
readers.set(0x000b, readCampaignRequest);
|
readers.set(0x000b, readCampaignRequest);
|
||||||
readers.set(0x000d, readRegisterRequest);
|
readers.set(0x000d, readRegisterRequest);
|
||||||
readers.set(0x000f, readLookupRequest2);
|
readers.set(0x000f, readLookupRequest2);
|
||||||
|
readers.set(0x0011, readFeliCaLookupRequest2);
|
||||||
readers.set(0x0064, readHelloRequest);
|
readers.set(0x0064, readHelloRequest);
|
||||||
readers.set(0x0066, readGoodbyeRequest);
|
readers.set(0x0066, readGoodbyeRequest);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ registerLevels.set("segaid", 2);
|
||||||
function begin(length: number) {
|
function begin(length: number) {
|
||||||
const buf = Buffer.alloc(length);
|
const buf = Buffer.alloc(length);
|
||||||
|
|
||||||
buf.writeUInt16LE(0xa13e, 0x0000); // Magic?
|
buf.writeUInt16LE(0xa13e, 0x0000); // Magic: aime
|
||||||
buf.writeUInt16LE(0x3087, 0x0002); // ???
|
buf.writeUInt16LE(0x3087, 0x0002); // ???
|
||||||
buf.writeUInt16LE(length, 0x0006);
|
buf.writeUInt16LE(length, 0x0006);
|
||||||
|
|
||||||
|
|
@ -43,6 +43,18 @@ export class Encoder extends Transform {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "felica_lookup2":
|
||||||
|
buf = begin(0x0140);
|
||||||
|
buf.writeUInt16LE(0x0012, 0x0004); // cmd code
|
||||||
|
buf.writeUInt16LE(msg.status, 0x0008);
|
||||||
|
buf.writeInt32LE(msg.aimeId || -1, 0x0020);
|
||||||
|
buf.writeUInt32LE(0xffffffff, 0x0024); // FF
|
||||||
|
buf.writeUInt32LE(0xffffffff, 0x0028); // FF
|
||||||
|
buf.write(msg.accessCode, 0x002c, "hex");
|
||||||
|
buf.writeUInt16LE(0x0001, 0x0037); // 00 01
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case "hello":
|
case "hello":
|
||||||
buf = begin(0x0020);
|
buf = begin(0x0020);
|
||||||
buf.writeUInt16LE(0x0065, 0x0004); // cmd code
|
buf.writeUInt16LE(0x0065, 0x0004); // cmd code
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,28 @@ function feliCaLookup(
|
||||||
return { type: req.type, status: 1, accessCode };
|
return { type: req.type, status: 1, accessCode };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function feliCaLookup2(
|
||||||
|
rep: Repositories,
|
||||||
|
req: Req.FeliCaLookup2Request,
|
||||||
|
now: Date
|
||||||
|
): Promise<Res.FeliCaLookup2Response> {
|
||||||
|
debug("FeliCa access code lookup");
|
||||||
|
|
||||||
|
const num = BigInt("0x" + req.idm);
|
||||||
|
let accessCode = num.toString();
|
||||||
|
|
||||||
|
while (accessCode.length < 20) {
|
||||||
|
accessCode = "0" + accessCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: req.type,
|
||||||
|
status: 1,
|
||||||
|
accessCode,
|
||||||
|
aimeId: await rep.cards().lookup(accessCode, now),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function lookup(
|
async function lookup(
|
||||||
rep: Repositories,
|
rep: Repositories,
|
||||||
req: Req.LookupRequest,
|
req: Req.LookupRequest,
|
||||||
|
|
@ -115,6 +137,9 @@ export async function dispatch(
|
||||||
case "felica_lookup":
|
case "felica_lookup":
|
||||||
return feliCaLookup(rep, req, now);
|
return feliCaLookup(rep, req, now);
|
||||||
|
|
||||||
|
case "felica_lookup2":
|
||||||
|
return feliCaLookup2(rep, req, now);
|
||||||
|
|
||||||
case "lookup":
|
case "lookup":
|
||||||
return lookup(rep, req, now);
|
return lookup(rep, req, now);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,12 @@ export interface FeliCaLookupRequest extends AimeRequestBase {
|
||||||
pmm: string;
|
pmm: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FeliCaLookup2Request extends AimeRequestBase {
|
||||||
|
type: "felica_lookup2";
|
||||||
|
idm: string;
|
||||||
|
pmm: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RegisterRequest extends AimeRequestBase {
|
export interface RegisterRequest extends AimeRequestBase {
|
||||||
type: "register";
|
type: "register";
|
||||||
luid: string;
|
luid: string;
|
||||||
|
|
@ -50,6 +56,7 @@ export interface GoodbyeRequest {
|
||||||
|
|
||||||
export type AimeRequest =
|
export type AimeRequest =
|
||||||
| FeliCaLookupRequest
|
| FeliCaLookupRequest
|
||||||
|
| FeliCaLookup2Request
|
||||||
| CampaignRequest
|
| CampaignRequest
|
||||||
| GoodbyeRequest
|
| GoodbyeRequest
|
||||||
| HelloRequest
|
| HelloRequest
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,12 @@ export interface FeliCaLookupResponse extends AimeResponseBase {
|
||||||
accessCode: string;
|
accessCode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FeliCaLookup2Response extends AimeResponseBase {
|
||||||
|
type: "felica_lookup2";
|
||||||
|
accessCode: string;
|
||||||
|
aimeId?: AimeId;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CampaignResponse extends AimeResponseBase {
|
export interface CampaignResponse extends AimeResponseBase {
|
||||||
type: "campaign";
|
type: "campaign";
|
||||||
}
|
}
|
||||||
|
|
@ -44,6 +50,7 @@ export interface RegisterResponse extends AimeResponseBase {
|
||||||
|
|
||||||
export type AimeResponse =
|
export type AimeResponse =
|
||||||
| FeliCaLookupResponse
|
| FeliCaLookupResponse
|
||||||
|
| FeliCaLookup2Response
|
||||||
| CampaignResponse
|
| CampaignResponse
|
||||||
| HelloResponse
|
| HelloResponse
|
||||||
| LogResponse
|
| LogResponse
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ class CardRepositoryImpl implements CardRepository {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = row.id;
|
const id = row.id!;
|
||||||
const extId = row.ext_id;
|
const extId = row.ext_id!;
|
||||||
|
|
||||||
const touchSql = sql
|
const touchSql = sql
|
||||||
.update("aime_player")
|
.update("aime_player")
|
||||||
|
|
|
||||||
|
|
@ -104,30 +104,32 @@ async function migratedb(
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function checkdb(db: DataSource): Promise<void> {
|
export default async function checkdb(db: DataSource): Promise<void> {
|
||||||
const stmt = sql.select("schemaver").from("meta");
|
let schemaver: number | undefined;
|
||||||
let maybe: number | undefined;
|
|
||||||
|
|
||||||
try {
|
await db.maintenance(async txn => {
|
||||||
const row = await db.transaction(txn => txn.fetchRow(stmt));
|
const stmt = sql.select("schemaver").from("meta");
|
||||||
|
|
||||||
if (row !== undefined) {
|
try {
|
||||||
maybe = parseInt(row.schemaver);
|
const row = await txn.fetchRow(stmt);
|
||||||
|
|
||||||
|
if (row !== undefined) {
|
||||||
|
schemaver = parseInt(row.schemaver!);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return await initdb(txn);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
return db.transaction(initdb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maybe === undefined) {
|
if (schemaver === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Database corrupted: `meta` table singleton row is missing"
|
"Database corrupted: `meta` table singleton row is missing"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const schemaver = maybe;
|
schemaver = await migratedb(txn, schemaver);
|
||||||
const newver = await db.transaction(txn => migratedb(txn, schemaver));
|
});
|
||||||
|
|
||||||
if (newver !== undefined) {
|
if (schemaver !== undefined) {
|
||||||
debug("Upgraded database to version %s", newver);
|
debug("Upgraded database to version %s", schemaver);
|
||||||
|
|
||||||
await db.vacuum();
|
await db.vacuum();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,30 @@
|
||||||
|
import { GameChargeJson } from "../proto/gameCharge";
|
||||||
import { Repositories } from "../repo";
|
import { Repositories } from "../repo";
|
||||||
import { GetGameChargeRequest } from "../request/getGameCharge";
|
import { GetGameChargeRequest } from "../request/getGameCharge";
|
||||||
import { GetGameChargeResponse } from "../response/getGameCharge";
|
import { GetGameChargeResponse } from "../response/getGameCharge";
|
||||||
|
import { CHARGE_IDS } from "../static/charge";
|
||||||
|
|
||||||
export default async function getGameCharge(
|
export default async function getGameCharge(
|
||||||
rep: Repositories,
|
rep: Repositories,
|
||||||
req: GetGameChargeRequest
|
req: GetGameChargeRequest
|
||||||
): Promise<GetGameChargeResponse> {
|
): Promise<GetGameChargeResponse> {
|
||||||
|
const gameChargeList: GameChargeJson[] = [];
|
||||||
|
|
||||||
|
for (const [i, charge] of CHARGE_IDS.entries()) {
|
||||||
|
gameChargeList.push({
|
||||||
|
chargeId: charge.id.toString(),
|
||||||
|
orderId: (i+1).toString(),
|
||||||
|
price: charge.price.toString(),
|
||||||
|
salePrice: charge.salePrice.toString(),
|
||||||
|
startDate: "2017-12-05 07:00:00.0",
|
||||||
|
endDate: "2029-12-31 23:59:59.0",
|
||||||
|
saleStartDate: "2017-12-05 07:00:00.0",
|
||||||
|
saleEndDate: "2030-12-31 23:59:59.0"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
length: "0",
|
length: gameChargeList.length.toString(),
|
||||||
gameChargeList: [],
|
gameChargeList,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,27 @@
|
||||||
|
import { GameEventJson } from "../proto/gameEvent";
|
||||||
import { Repositories } from "../repo";
|
import { Repositories } from "../repo";
|
||||||
import { GetGameEventRequest } from "../request/getGameEvent";
|
import { GetGameEventRequest } from "../request/getGameEvent";
|
||||||
import { GetGameEventResponse } from "../response/getGameEvent";
|
import { GetGameEventResponse } from "../response/getGameEvent";
|
||||||
|
import { EVENT_IDS } from "../static/event";
|
||||||
|
|
||||||
export default async function getGameEvent(
|
export default async function getGameEvent(
|
||||||
rep: Repositories,
|
rep: Repositories,
|
||||||
req: GetGameEventRequest
|
req: GetGameEventRequest
|
||||||
): Promise<GetGameEventResponse> {
|
): Promise<GetGameEventResponse> {
|
||||||
|
const gameEventList: GameEventJson[] = [];
|
||||||
|
|
||||||
|
for (const id of EVENT_IDS) {
|
||||||
|
gameEventList.push({
|
||||||
|
type: req.type,
|
||||||
|
id: id.toString(),
|
||||||
|
startDate: "2017-12-05 07:00:00.0",
|
||||||
|
endDate: "2099-12-31 00:00:00.0",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: req.type,
|
type: req.type,
|
||||||
length: "0",
|
length: gameEventList.length.toString(),
|
||||||
gameEventList: [],
|
gameEventList,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { writeDate } from "../proto/base";
|
||||||
import { Repositories } from "../repo";
|
import { Repositories } from "../repo";
|
||||||
import { GetGameSettingRequest } from "../request/getGameSetting";
|
import { GetGameSettingRequest } from "../request/getGameSetting";
|
||||||
import { GetGameSettingResponse } from "../response/getGameSetting";
|
import { GetGameSettingResponse } from "../response/getGameSetting";
|
||||||
|
|
@ -6,19 +7,25 @@ export default async function getGameSetting(
|
||||||
rep: Repositories,
|
rep: Repositories,
|
||||||
req: GetGameSettingRequest
|
req: GetGameSettingRequest
|
||||||
): Promise<GetGameSettingResponse> {
|
): Promise<GetGameSettingResponse> {
|
||||||
|
const rebootStartTime = new Date();
|
||||||
|
rebootStartTime.setHours(rebootStartTime.getHours() - 3);
|
||||||
|
|
||||||
|
const rebootEndTime = new Date();
|
||||||
|
rebootEndTime.setHours(rebootEndTime.getHours() - 2);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
gameSetting: {
|
gameSetting: {
|
||||||
dataVersion: "1",
|
dataVersion: "1",
|
||||||
isMaintenance: "false",
|
isMaintenance: "false",
|
||||||
requestInterval: "10",
|
requestInterval: "10",
|
||||||
rebootStartTime: "0",
|
rebootStartTime: writeDate(rebootStartTime),
|
||||||
rebootEndTime: "0",
|
rebootEndTime: writeDate(rebootEndTime),
|
||||||
isBackgroundDistribute: "false",
|
isBackgroundDistribute: "false",
|
||||||
maxCountCharacter: "999",
|
maxCountCharacter: "300",
|
||||||
maxCountItem: "999",
|
maxCountItem: "300",
|
||||||
maxCountMusic: "999",
|
maxCountMusic: "100",
|
||||||
},
|
},
|
||||||
isDumpUpload: "false",
|
isDumpUpload: "false",
|
||||||
isAou: "false",
|
isAou: "true",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { readAimeId } from "../proto/base";
|
||||||
|
import { writeUserCharge } from "../proto/userCharge";
|
||||||
import { Repositories } from "../repo";
|
import { Repositories } from "../repo";
|
||||||
import { GetUserChargeRequest } from "../request/getUserCharge";
|
import { GetUserChargeRequest } from "../request/getUserCharge";
|
||||||
import { GetUserChargeResponse } from "../response/getUserCharge";
|
import { GetUserChargeResponse } from "../response/getUserCharge";
|
||||||
|
|
@ -6,9 +8,14 @@ export default async function getUserCharge(
|
||||||
rep: Repositories,
|
rep: Repositories,
|
||||||
req: GetUserChargeRequest
|
req: GetUserChargeRequest
|
||||||
): Promise<GetUserChargeResponse> {
|
): Promise<GetUserChargeResponse> {
|
||||||
|
const aimeId = readAimeId(req.userId);
|
||||||
|
|
||||||
|
const profileId = await rep.userData().lookup(aimeId);
|
||||||
|
const items = await rep.userCharge().load(profileId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userId: req.userId,
|
userId: req.userId,
|
||||||
length: "0",
|
length: items.length.toString(),
|
||||||
userChargeList: [],
|
userChargeList: items.map(writeUserCharge),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ export default async function getUserCourse(
|
||||||
.userCourse()
|
.userCourse()
|
||||||
.load(profileId, { limit: maxCount, offset: nextIndex });
|
.load(profileId, { limit: maxCount, offset: nextIndex });
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userId: req.userId,
|
userId: req.userId,
|
||||||
length: items.length.toString(),
|
length: items.length.toString(),
|
||||||
|
|
|
||||||
23
src/chunithm/handler/getUserFavoriteMusic.ts
Normal file
23
src/chunithm/handler/getUserFavoriteMusic.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Repositories } from "../repo";
|
||||||
|
import { GetUserFavoriteMusicRequest } from "../request/getUserFavoriteMusic";
|
||||||
|
import { GetUserFavoriteMusicResponse } from "../response/getUserFavoriteMusic";
|
||||||
|
import { readAimeId } from "../proto/base";
|
||||||
|
|
||||||
|
export default async function getUserFavoriteMusic(
|
||||||
|
rep: Repositories,
|
||||||
|
req: GetUserFavoriteMusicRequest
|
||||||
|
): Promise<GetUserFavoriteMusicResponse> {
|
||||||
|
const aimeId = readAimeId(req.userId);
|
||||||
|
|
||||||
|
const profileId = await rep.userData().lookup(aimeId);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `Chunithm Amazon Plus` does not appear to save a favorites list and there
|
||||||
|
* is no user-accessible favorites function from what I can tell.
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
userId: req.userId,
|
||||||
|
length: "0",
|
||||||
|
userFavoriteMusicList: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -47,7 +47,7 @@ export default async function getUserItem(
|
||||||
|
|
||||||
// Pack the next pagination cookie into bigint compound form
|
// Pack the next pagination cookie into bigint compound form
|
||||||
|
|
||||||
const xout = itemKindN * itemKindMul + BigInt(maxCount + items.length);
|
const xout = itemKindN * itemKindMul + nextIndexN + BigInt(items.length);
|
||||||
|
|
||||||
// Done:
|
// Done:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { Repositories } from "../repo";
|
||||||
import { GetUserRecentRatingRequest } from "../request/getUserRecentRating";
|
import { GetUserRecentRatingRequest } from "../request/getUserRecentRating";
|
||||||
import { GetUserRecentRatingResponse } from "../response/getUserRecentRating";
|
import { GetUserRecentRatingResponse } from "../response/getUserRecentRating";
|
||||||
import { readAimeId } from "../proto/base";
|
import { readAimeId } from "../proto/base";
|
||||||
import { writeUserRecentRatingFromLog } from "../proto/userRecentRating";
|
import { writeUserRecentRating } from "../proto/userRecentRating";
|
||||||
|
|
||||||
export default async function getUserRecentRating(
|
export default async function getUserRecentRating(
|
||||||
rep: Repositories,
|
rep: Repositories,
|
||||||
|
|
@ -12,11 +12,11 @@ export default async function getUserRecentRating(
|
||||||
|
|
||||||
const profileId = await rep.userData().lookup(aimeId);
|
const profileId = await rep.userData().lookup(aimeId);
|
||||||
// Return recent 30 plays to calculate rating
|
// Return recent 30 plays to calculate rating
|
||||||
const items = await rep.userPlaylog().loadLatest(profileId, 30);
|
const items = await rep.userRecentRating().load(profileId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userId: req.userId,
|
userId: req.userId,
|
||||||
length: items.length.toString(),
|
length: items.length.toString(),
|
||||||
userRecentRatingList: items.map(writeUserRecentRatingFromLog),
|
userRecentRatingList: items.map(writeUserRecentRating),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import getUserCourse from "./getUserCourse";
|
||||||
import getUserData from "./getUserData";
|
import getUserData from "./getUserData";
|
||||||
import getUserDataEx from "./getUserDataEx";
|
import getUserDataEx from "./getUserDataEx";
|
||||||
import getUserDuel from "./getUserDuel";
|
import getUserDuel from "./getUserDuel";
|
||||||
|
import getUserFavoriteMusic from "./getUserFavoriteMusic";
|
||||||
import getUserItem from "./getUserItem";
|
import getUserItem from "./getUserItem";
|
||||||
import getUserMap from "./getUserMap";
|
import getUserMap from "./getUserMap";
|
||||||
import getUserMusic from "./getUserMusic";
|
import getUserMusic from "./getUserMusic";
|
||||||
|
|
@ -32,6 +33,7 @@ import upsertClientError from "./upsertClientError";
|
||||||
import upsertClientSetting from "./upsertClientSetting";
|
import upsertClientSetting from "./upsertClientSetting";
|
||||||
import upsertClientTestmode from "./upsertClientTestmode";
|
import upsertClientTestmode from "./upsertClientTestmode";
|
||||||
import upsertUserAll from "./upsertUserAll";
|
import upsertUserAll from "./upsertUserAll";
|
||||||
|
import upsertUserChargelogApi from "./upsertUserChargelogApi";
|
||||||
import createSqlWrapper from "../sql";
|
import createSqlWrapper from "../sql";
|
||||||
import { DataSource } from "../../sql";
|
import { DataSource } from "../../sql";
|
||||||
|
|
||||||
|
|
@ -98,12 +100,14 @@ export default function chunithm(db: DataSource) {
|
||||||
wrapper.rpc("/GetUserDataApi", getUserData);
|
wrapper.rpc("/GetUserDataApi", getUserData);
|
||||||
wrapper.rpc("/GetUserDataExApi", getUserDataEx);
|
wrapper.rpc("/GetUserDataExApi", getUserDataEx);
|
||||||
wrapper.rpc("/GetUserDuelApi", getUserDuel);
|
wrapper.rpc("/GetUserDuelApi", getUserDuel);
|
||||||
|
wrapper.rpc("/GetUserFavoriteMusicApi", getUserFavoriteMusic);
|
||||||
wrapper.rpc("/GetUserItemApi", getUserItem);
|
wrapper.rpc("/GetUserItemApi", getUserItem);
|
||||||
wrapper.rpc("/GetUserMapApi", getUserMap);
|
wrapper.rpc("/GetUserMapApi", getUserMap);
|
||||||
wrapper.rpc("/GetUserMusicApi", getUserMusic);
|
wrapper.rpc("/GetUserMusicApi", getUserMusic);
|
||||||
wrapper.rpc("/GetUserOptionApi", getUserOption);
|
wrapper.rpc("/GetUserOptionApi", getUserOption);
|
||||||
wrapper.rpc("/GetUserOptionExApi", getUserOptionEx);
|
wrapper.rpc("/GetUserOptionExApi", getUserOptionEx);
|
||||||
wrapper.rpc("/GetUserPreviewApi", getUserPreview);
|
wrapper.rpc("/GetUserPreviewApi", getUserPreview);
|
||||||
|
wrapper.rpc("/GetUserRecentPlayerApi", getUserRecentRating);
|
||||||
wrapper.rpc("/GetUserRecentRatingApi", getUserRecentRating);
|
wrapper.rpc("/GetUserRecentRatingApi", getUserRecentRating);
|
||||||
wrapper.rpc("/GetUserRegionApi", getUserRegion);
|
wrapper.rpc("/GetUserRegionApi", getUserRegion);
|
||||||
wrapper.rpc("/UpsertClientBookkeepingApi", upsertClientBookkeeping);
|
wrapper.rpc("/UpsertClientBookkeepingApi", upsertClientBookkeeping);
|
||||||
|
|
@ -112,6 +116,7 @@ export default function chunithm(db: DataSource) {
|
||||||
wrapper.rpc("/UpsertClientSettingApi", upsertClientSetting);
|
wrapper.rpc("/UpsertClientSettingApi", upsertClientSetting);
|
||||||
wrapper.rpc("/UpsertClientTestmodeApi", upsertClientTestmode);
|
wrapper.rpc("/UpsertClientTestmodeApi", upsertClientTestmode);
|
||||||
wrapper.rpc("/UpsertUserAllApi", upsertUserAll);
|
wrapper.rpc("/UpsertUserAllApi", upsertUserAll);
|
||||||
|
wrapper.rpc("/UpsertUserChargelogApi", upsertUserChargelogApi)
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ import { readUserActivity } from "../proto/userActivity";
|
||||||
import { readUserDataEx } from "../proto/userDataEx";
|
import { readUserDataEx } from "../proto/userDataEx";
|
||||||
import { readUserDuelList } from "../proto/userDuelList";
|
import { readUserDuelList } from "../proto/userDuelList";
|
||||||
import { readUserPlaylog } from "../proto/userPlaylog";
|
import { readUserPlaylog } from "../proto/userPlaylog";
|
||||||
|
import { readUserCharge } from "../proto/userCharge";
|
||||||
|
import { readUserRecentRating } from "../proto/userRecentRating";
|
||||||
import { readUserCourse } from "../proto/userCourse";
|
import { readUserCourse } from "../proto/userCourse";
|
||||||
|
|
||||||
// It shouldn't need to be said really, but seeing as this message (A) requires
|
// It shouldn't need to be said really, but seeing as this message (A) requires
|
||||||
|
|
@ -67,6 +69,10 @@ export default async function upsertUserAll(
|
||||||
await rep.userPlaylog().save(profileId, readUserPlaylog(item));
|
await rep.userPlaylog().save(profileId, readUserPlaylog(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const item of payload.userChargeList || []) {
|
||||||
|
await rep.userCharge().save(profileId, readUserCharge(item));
|
||||||
|
}
|
||||||
|
|
||||||
for (const item of payload.userCourseList || []) {
|
for (const item of payload.userCourseList || []) {
|
||||||
await rep.userCourse().save(profileId, readUserCourse(item));
|
await rep.userCourse().save(profileId, readUserCourse(item));
|
||||||
}
|
}
|
||||||
|
|
@ -79,5 +85,12 @@ export default async function upsertUserAll(
|
||||||
await rep.userDuelList().save(profileId, readUserDuelList(item));
|
await rep.userDuelList().save(profileId, readUserDuelList(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await rep
|
||||||
|
.userRecentRating()
|
||||||
|
.save(
|
||||||
|
profileId,
|
||||||
|
(payload.userRecentRatingList || []).map(readUserRecentRating)
|
||||||
|
);
|
||||||
|
|
||||||
return { returnCode: "1" };
|
return { returnCode: "1" };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
src/chunithm/handler/upsertUserChargelogApi.ts
Normal file
12
src/chunithm/handler/upsertUserChargelogApi.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Repositories } from "../repo";
|
||||||
|
import { UpsertUserChargelogApiRequest } from "../request/upsertUserChargelogApi";
|
||||||
|
import { UpsertUserChargelogApiResponse } from "../response/upsertUserChargelogApi";
|
||||||
|
|
||||||
|
export default async function UpsertUserChargelogApi(
|
||||||
|
rep: Repositories,
|
||||||
|
req: UpsertUserChargelogApiRequest
|
||||||
|
): Promise<UpsertUserChargelogApiResponse> {
|
||||||
|
return {
|
||||||
|
returnCode: "1",
|
||||||
|
};
|
||||||
|
}
|
||||||
9
src/chunithm/model/userCharge.ts
Normal file
9
src/chunithm/model/userCharge.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
export interface UserChargeItem {
|
||||||
|
chargeId: number;
|
||||||
|
stock: number;
|
||||||
|
purchaseDate: Date;
|
||||||
|
validDate: Date;
|
||||||
|
param1: number;
|
||||||
|
param2: number;
|
||||||
|
paramDate: Date;
|
||||||
|
}
|
||||||
20
src/chunithm/proto/userCharge.ts
Normal file
20
src/chunithm/proto/userCharge.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Crush, readDate, writeObject } from "./base";
|
||||||
|
import { UserChargeItem } from "../model/userCharge";
|
||||||
|
|
||||||
|
export type UserChargeJson = Crush<UserChargeItem>;
|
||||||
|
|
||||||
|
export function readUserCharge(json: UserChargeJson): UserChargeItem {
|
||||||
|
return {
|
||||||
|
chargeId: parseInt(json.chargeId),
|
||||||
|
stock: parseInt(json.stock),
|
||||||
|
purchaseDate: readDate(json.purchaseDate),
|
||||||
|
validDate: readDate(json.validDate),
|
||||||
|
param1: parseInt(json.param1),
|
||||||
|
param2: parseInt(json.param2),
|
||||||
|
paramDate: readDate(json.paramDate),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeUserCharge(obj: UserChargeItem): UserChargeJson {
|
||||||
|
return writeObject(obj);
|
||||||
|
}
|
||||||
|
|
@ -20,15 +20,3 @@ export function writeUserRecentRating(
|
||||||
): UserRecentRatingJson {
|
): UserRecentRatingJson {
|
||||||
return writeObject(obj);
|
return writeObject(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function writeUserRecentRatingFromLog(
|
|
||||||
obj: UserPlaylogItem
|
|
||||||
): UserRecentRatingJson {
|
|
||||||
return {
|
|
||||||
musicId: obj.musicId.toString(),
|
|
||||||
difficultId: obj.level.toString(),
|
|
||||||
// game version not saved in play log, just return a fixed version now
|
|
||||||
romVersionCode: "1030000",
|
|
||||||
score: obj.score.toString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { UserCourseRepository } from "./userCourse";
|
|
||||||
|
|
||||||
export { Page } from "./_defs";
|
export { Page } from "./_defs";
|
||||||
|
|
||||||
import { UserActivityRepository } from "./userActivity";
|
import { UserActivityRepository } from "./userActivity";
|
||||||
import { UserCharacterRepository } from "./userCharacter";
|
import { UserCharacterRepository } from "./userCharacter";
|
||||||
|
import { UserCourseRepository } from "./userCourse";
|
||||||
import { UserDataRepository } from "./userData";
|
import { UserDataRepository } from "./userData";
|
||||||
import { UserDataExRepository } from "./userDataEx";
|
import { UserDataExRepository } from "./userDataEx";
|
||||||
import { UserDuelListRepository } from "./userDuelList";
|
import { UserDuelListRepository } from "./userDuelList";
|
||||||
|
|
@ -13,12 +12,16 @@ import { UserItemRepository } from "./userItem";
|
||||||
import { UserMapRepository } from "./userMap";
|
import { UserMapRepository } from "./userMap";
|
||||||
import { UserMusicRepository } from "./userMusic";
|
import { UserMusicRepository } from "./userMusic";
|
||||||
import { UserPlaylogRepository } from "./userPlaylog";
|
import { UserPlaylogRepository } from "./userPlaylog";
|
||||||
|
import { UserChargeRepository } from "./userCharge";
|
||||||
|
import { UserRecentRatingRepository } from "./userRecentRating";
|
||||||
|
|
||||||
export interface Repositories {
|
export interface Repositories {
|
||||||
userActivity(): UserActivityRepository;
|
userActivity(): UserActivityRepository;
|
||||||
|
|
||||||
userCharacter(): UserCharacterRepository;
|
userCharacter(): UserCharacterRepository;
|
||||||
|
|
||||||
|
userCourse(): UserCourseRepository;
|
||||||
|
|
||||||
userData(): UserDataRepository;
|
userData(): UserDataRepository;
|
||||||
|
|
||||||
userDataEx(): UserDataExRepository;
|
userDataEx(): UserDataExRepository;
|
||||||
|
|
@ -37,5 +40,9 @@ export interface Repositories {
|
||||||
|
|
||||||
userPlaylog(): UserPlaylogRepository;
|
userPlaylog(): UserPlaylogRepository;
|
||||||
|
|
||||||
|
userCharge(): UserChargeRepository;
|
||||||
|
|
||||||
userCourse(): UserCourseRepository;
|
userCourse(): UserCourseRepository;
|
||||||
|
|
||||||
|
userRecentRating(): UserRecentRatingRepository;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
src/chunithm/repo/userCharge.ts
Normal file
4
src/chunithm/repo/userCharge.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { UserChargeItem } from "../model/userCharge";
|
||||||
|
import { RepositoryN } from "./_defs";
|
||||||
|
|
||||||
|
export type UserChargeRepository = RepositoryN<UserChargeItem>;
|
||||||
12
src/chunithm/repo/userRecentRating.ts
Normal file
12
src/chunithm/repo/userRecentRating.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { UserDataItem } from "../model/userData";
|
||||||
|
import { UserRecentRatingItem } from "../model/userRecentRating";
|
||||||
|
import { Id } from "../../model";
|
||||||
|
|
||||||
|
export interface UserRecentRatingRepository {
|
||||||
|
load(profileId: Id<UserDataItem>): Promise<UserRecentRatingItem[]>;
|
||||||
|
|
||||||
|
save(
|
||||||
|
profileId: Id<UserDataItem>,
|
||||||
|
objs: UserRecentRatingItem[]
|
||||||
|
): Promise<void>;
|
||||||
|
}
|
||||||
4
src/chunithm/request/getUserFavoriteMusic.ts
Normal file
4
src/chunithm/request/getUserFavoriteMusic.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface GetUserFavoriteMusicRequest {
|
||||||
|
/** Integer, AiMe ID */
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import { UserMusicDetailJson } from "../proto/userMusic";
|
||||||
import { UserActivityJson } from "../proto/userActivity";
|
import { UserActivityJson } from "../proto/userActivity";
|
||||||
import { UserRecentRatingJson } from "../proto/userRecentRating";
|
import { UserRecentRatingJson } from "../proto/userRecentRating";
|
||||||
import { UserPlaylogJson } from "../proto/userPlaylog";
|
import { UserPlaylogJson } from "../proto/userPlaylog";
|
||||||
|
import { UserChargeJson } from "../proto/userCharge";
|
||||||
import { UserCourseJson } from "../proto/userCourse";
|
import { UserCourseJson } from "../proto/userCourse";
|
||||||
import { UserDataExJson } from "../proto/userDataEx";
|
import { UserDataExJson } from "../proto/userDataEx";
|
||||||
import { UserDuelListJson } from "../proto/userDuelList";
|
import { UserDuelListJson } from "../proto/userDuelList";
|
||||||
|
|
@ -27,6 +28,7 @@ export interface UpsertUserAllRequest {
|
||||||
userActivityList?: UserActivityJson[];
|
userActivityList?: UserActivityJson[];
|
||||||
userRecentRatingList?: UserRecentRatingJson[];
|
userRecentRatingList?: UserRecentRatingJson[];
|
||||||
userPlaylogList?: UserPlaylogJson[];
|
userPlaylogList?: UserPlaylogJson[];
|
||||||
|
userChargeList?: UserChargeJson[];
|
||||||
userCourseList?: UserCourseJson[];
|
userCourseList?: UserCourseJson[];
|
||||||
userDataEx?: UserDataExJson[];
|
userDataEx?: UserDataExJson[];
|
||||||
userDuelList?: UserDuelListJson[];
|
userDuelList?: UserDuelListJson[];
|
||||||
|
|
|
||||||
24
src/chunithm/request/upsertUserChargelogApi.ts
Normal file
24
src/chunithm/request/upsertUserChargelogApi.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
export interface UpsertUserChargelogApiRequest {
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
userChargelog: {
|
||||||
|
chargeId: string,
|
||||||
|
price: string,
|
||||||
|
purchaseDate: Date,
|
||||||
|
playCount: string,
|
||||||
|
playerRating: string,
|
||||||
|
placeId: string,
|
||||||
|
regionId: string,
|
||||||
|
clientId: string
|
||||||
|
};
|
||||||
|
|
||||||
|
userCharge: {
|
||||||
|
chargeId: string,
|
||||||
|
stock: string,
|
||||||
|
purchaseDate: Date,
|
||||||
|
validDate: Date,
|
||||||
|
param1: string,
|
||||||
|
param2: string,
|
||||||
|
paramDate: Date
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { UserChargeJson } from "../proto/userCharge";
|
||||||
|
|
||||||
export interface GetUserChargeResponse {
|
export interface GetUserChargeResponse {
|
||||||
/** Integer, AiMe ID */
|
/** Integer, AiMe ID */
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
@ -5,6 +7,5 @@ export interface GetUserChargeResponse {
|
||||||
/** Integer, number of results returned */
|
/** Integer, number of results returned */
|
||||||
length: string;
|
length: string;
|
||||||
|
|
||||||
/** TBD */
|
userChargeList: UserChargeJson[];
|
||||||
userChargeList: [];
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
src/chunithm/response/getUserFavoriteMusic.ts
Normal file
10
src/chunithm/response/getUserFavoriteMusic.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
export interface GetUserFavoriteMusicResponse {
|
||||||
|
/** Integer, AiMe ID */
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
/** Integer, number of results returned */
|
||||||
|
length: string;
|
||||||
|
|
||||||
|
/** TBD */
|
||||||
|
userFavoriteMusicList: [];
|
||||||
|
}
|
||||||
3
src/chunithm/response/upsertUserChargelogApi.ts
Normal file
3
src/chunithm/response/upsertUserChargelogApi.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export interface UpsertUserChargelogApiResponse {
|
||||||
|
returnCode: string;
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { SqlUserActivityRepository } from "./userActivity";
|
import { SqlUserActivityRepository } from "./userActivity";
|
||||||
import { SqlUserCharacterRepository } from "./userCharacter";
|
import { SqlUserCharacterRepository } from "./userCharacter";
|
||||||
|
import { SqlUserChargeRepository } from "./userCharge";
|
||||||
|
import { SqlUserCourseRepository } from "./userCourse";
|
||||||
import { SqlUserDataRepository } from "./userData";
|
import { SqlUserDataRepository } from "./userData";
|
||||||
import { SqlUserDataExRepository } from "./userDataEx";
|
import { SqlUserDataExRepository } from "./userDataEx";
|
||||||
import { SqlUserDuelListRepository } from "./userDuelList";
|
import { SqlUserDuelListRepository } from "./userDuelList";
|
||||||
|
|
@ -9,9 +11,12 @@ import { SqlUserItemRepository } from "./userItem";
|
||||||
import { SqlUserMapRepository } from "./userMap";
|
import { SqlUserMapRepository } from "./userMap";
|
||||||
import { SqlUserMusicRepository } from "./userMusic";
|
import { SqlUserMusicRepository } from "./userMusic";
|
||||||
import { SqlUserPlaylogRepository } from "./userPlaylog";
|
import { SqlUserPlaylogRepository } from "./userPlaylog";
|
||||||
|
import { SqlUserRecentRatingRepository } from "./userRecentRating";
|
||||||
import { Repositories } from "../repo";
|
import { Repositories } from "../repo";
|
||||||
import { UserActivityRepository } from "../repo/userActivity";
|
import { UserActivityRepository } from "../repo/userActivity";
|
||||||
import { UserCharacterRepository } from "../repo/userCharacter";
|
import { UserCharacterRepository } from "../repo/userCharacter";
|
||||||
|
import { UserChargeRepository } from "../repo/userCharge";
|
||||||
|
import { UserCourseRepository } from "../repo/userCourse";
|
||||||
import { UserDataRepository } from "../repo/userData";
|
import { UserDataRepository } from "../repo/userData";
|
||||||
import { UserDataExRepository } from "../repo/userDataEx";
|
import { UserDataExRepository } from "../repo/userDataEx";
|
||||||
import { UserDuelListRepository } from "../repo/userDuelList";
|
import { UserDuelListRepository } from "../repo/userDuelList";
|
||||||
|
|
@ -21,9 +26,8 @@ import { UserItemRepository } from "../repo/userItem";
|
||||||
import { UserMapRepository } from "../repo/userMap";
|
import { UserMapRepository } from "../repo/userMap";
|
||||||
import { UserMusicRepository } from "../repo/userMusic";
|
import { UserMusicRepository } from "../repo/userMusic";
|
||||||
import { UserPlaylogRepository } from "../repo/userPlaylog";
|
import { UserPlaylogRepository } from "../repo/userPlaylog";
|
||||||
|
import { UserRecentRatingRepository } from "../repo/userRecentRating";
|
||||||
import { Transaction } from "../../sql";
|
import { Transaction } from "../../sql";
|
||||||
import { UserCourseRepository } from "../repo/userCourse";
|
|
||||||
import { SqlUserCourseRepository } from "./userCourse";
|
|
||||||
|
|
||||||
export class SqlRepositories implements Repositories {
|
export class SqlRepositories implements Repositories {
|
||||||
constructor(private readonly _txn: Transaction) {}
|
constructor(private readonly _txn: Transaction) {}
|
||||||
|
|
@ -36,6 +40,14 @@ export class SqlRepositories implements Repositories {
|
||||||
return new SqlUserCharacterRepository(this._txn);
|
return new SqlUserCharacterRepository(this._txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userCharge(): UserChargeRepository {
|
||||||
|
return new SqlUserChargeRepository(this._txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
userCourse(): UserCourseRepository {
|
||||||
|
return new SqlUserCourseRepository(this._txn);
|
||||||
|
}
|
||||||
|
|
||||||
userData(): UserDataRepository {
|
userData(): UserDataRepository {
|
||||||
return new SqlUserDataRepository(this._txn);
|
return new SqlUserDataRepository(this._txn);
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +84,7 @@ export class SqlRepositories implements Repositories {
|
||||||
return new SqlUserPlaylogRepository(this._txn);
|
return new SqlUserPlaylogRepository(this._txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
userCourse(): UserCourseRepository {
|
userRecentRating(): UserRecentRatingRepository {
|
||||||
return new SqlUserCourseRepository(this._txn);
|
return new SqlUserRecentRatingRepository(this._txn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,9 @@ export class SqlUserActivityRepository implements UserActivityRepository {
|
||||||
.select("*")
|
.select("*")
|
||||||
.from("cm_user_activity")
|
.from("cm_user_activity")
|
||||||
.where("profile_id", profileId)
|
.where("profile_id", profileId)
|
||||||
.where("kind", kind);
|
.where("kind", kind)
|
||||||
|
.orderBy("sort_number DESC")
|
||||||
|
.limit(100);
|
||||||
|
|
||||||
const rows = await this._txn.fetchRows(stmt);
|
const rows = await this._txn.fetchRows(stmt);
|
||||||
|
|
||||||
|
|
|
||||||
47
src/chunithm/sql/userCharge.ts
Normal file
47
src/chunithm/sql/userCharge.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import sql from "sql-bricks-postgres";
|
||||||
|
import { createSqlMapper, T } from "../../sql/util";
|
||||||
|
import { UserChargeRepository } from "../repo/userCharge";
|
||||||
|
import { Id } from "../../model";
|
||||||
|
import { Page } from "../repo";
|
||||||
|
import { UserDataItem } from "../model/userData";
|
||||||
|
import { UserChargeItem } from "../model/userCharge";
|
||||||
|
import { Transaction } from "../../sql";
|
||||||
|
|
||||||
|
const { readRow, writeRow, colNames } = createSqlMapper({
|
||||||
|
chargeId: T.number,
|
||||||
|
stock: T.number,
|
||||||
|
purchaseDate: T.Date,
|
||||||
|
validDate: T.Date,
|
||||||
|
param1: T.number,
|
||||||
|
param2: T.number,
|
||||||
|
paramDate: T.Date,
|
||||||
|
});
|
||||||
|
|
||||||
|
export class SqlUserChargeRepository implements UserChargeRepository {
|
||||||
|
constructor(private readonly _txn: Transaction) {}
|
||||||
|
|
||||||
|
async load(profileId: Id<UserDataItem>): Promise<UserChargeItem[]> {
|
||||||
|
const stmt = sql
|
||||||
|
.select("*")
|
||||||
|
.from("cm_user_charge")
|
||||||
|
.where("profile_id", profileId);
|
||||||
|
|
||||||
|
const rows = await this._txn.fetchRows(stmt);
|
||||||
|
|
||||||
|
return rows.map(readRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
save(profileId: Id<UserDataItem>, obj: UserChargeItem): Promise<void> {
|
||||||
|
const stmt = sql
|
||||||
|
.insert("cm_user_charge", {
|
||||||
|
id: this._txn.generateId(),
|
||||||
|
profile_id: profileId,
|
||||||
|
...writeRow(obj),
|
||||||
|
})
|
||||||
|
.onConflict("profile_id", "charge_id")
|
||||||
|
.doUpdate(colNames);
|
||||||
|
|
||||||
|
return this._txn.modify(stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -37,7 +37,10 @@ export class SqlUserCourseRepository implements UserCourseRepository {
|
||||||
.from("cm_user_course")
|
.from("cm_user_course")
|
||||||
.where("profile_id", profileId);
|
.where("profile_id", profileId);
|
||||||
|
|
||||||
if (page) {
|
/**
|
||||||
|
* UserCourse has no paging before CHUNITHM Amazon
|
||||||
|
*/
|
||||||
|
if (page && !isNaN(page.limit)) {
|
||||||
stmt.limit(page.limit).offset(page.offset);
|
stmt.limit(page.limit).offset(page.offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,47 @@ export class SqlUserDataExRepository implements UserDataExRepository {
|
||||||
const row = await this._txn.fetchRow(stmt);
|
const row = await this._txn.fetchRow(stmt);
|
||||||
|
|
||||||
if (row === undefined) {
|
if (row === undefined) {
|
||||||
throw new Error("UserDataEx record not found");
|
/**
|
||||||
|
* When upgrading from CHUNITHM Star Plus & earlier
|
||||||
|
* to Amazon, there is no UserDataEx, so we return
|
||||||
|
* a "default" row.
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
compatibleCmVersion: "",
|
||||||
|
medal: 0,
|
||||||
|
mapIconId: 0,
|
||||||
|
voiceId: 0,
|
||||||
|
ext1: 0,
|
||||||
|
ext2: 0,
|
||||||
|
ext3: 0,
|
||||||
|
ext4: 0,
|
||||||
|
ext5: 0,
|
||||||
|
ext6: 0,
|
||||||
|
ext7: 0,
|
||||||
|
ext8: 0,
|
||||||
|
ext9: 0,
|
||||||
|
ext10: 0,
|
||||||
|
ext11: 0,
|
||||||
|
ext12: 0,
|
||||||
|
ext13: 0,
|
||||||
|
ext14: 0,
|
||||||
|
ext15: 0,
|
||||||
|
ext16: 0,
|
||||||
|
ext17: 0,
|
||||||
|
ext18: 0,
|
||||||
|
ext19: 0,
|
||||||
|
ext20: 0,
|
||||||
|
extStr1: "",
|
||||||
|
extStr2: "",
|
||||||
|
extStr3: "",
|
||||||
|
extStr4: "",
|
||||||
|
extStr5: "",
|
||||||
|
extLong1: 0n,
|
||||||
|
extLong2: 0n,
|
||||||
|
extLong3: 0n,
|
||||||
|
extLong4: 0n,
|
||||||
|
extLong5: 0n,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return readRow(row);
|
return readRow(row);
|
||||||
|
|
|
||||||
|
|
@ -34,14 +34,22 @@ export class SqlUserMusicRepository implements UserMusicRepository {
|
||||||
profileId: Id<UserDataItem>,
|
profileId: Id<UserDataItem>,
|
||||||
page?: Page
|
page?: Page
|
||||||
): Promise<UserMusicDetailItem[]> {
|
): Promise<UserMusicDetailItem[]> {
|
||||||
|
const preStmt = sql
|
||||||
|
.select("DISTINCT(music_id)")
|
||||||
|
.from("cm_user_music")
|
||||||
|
.where("profile_id", profileId)
|
||||||
|
.orderBy("music_id");
|
||||||
|
if (page) {
|
||||||
|
preStmt.limit(page.limit).offset(page.offset);
|
||||||
|
}
|
||||||
|
const preRows = await this._txn.fetchRows(preStmt);
|
||||||
|
const musicIds = preRows.map(r => r.music_id);
|
||||||
|
|
||||||
const stmt = sql
|
const stmt = sql
|
||||||
.select("*")
|
.select("*")
|
||||||
.from("cm_user_music")
|
.from("cm_user_music")
|
||||||
.where("profile_id", profileId);
|
.where("profile_id", profileId)
|
||||||
|
.and(sql.in('music_id', musicIds));
|
||||||
if (page) {
|
|
||||||
stmt.limit(page.limit).offset(page.offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = await this._txn.fetchRows(stmt);
|
const rows = await this._txn.fetchRows(stmt);
|
||||||
|
|
||||||
|
|
|
||||||
60
src/chunithm/sql/userRecentRating.ts
Normal file
60
src/chunithm/sql/userRecentRating.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import sql from "sql-bricks-postgres";
|
||||||
|
|
||||||
|
import { Id } from "../../model";
|
||||||
|
import { UserDataItem } from "../model/userData";
|
||||||
|
import { UserRecentRatingItem } from "../model/userRecentRating";
|
||||||
|
import { UserRecentRatingRepository } from "../repo/userRecentRating";
|
||||||
|
import { Transaction } from "../../sql";
|
||||||
|
import { T, createSqlMapper } from "../../sql/util";
|
||||||
|
|
||||||
|
const { readRow, writeRow, colNames } = createSqlMapper({
|
||||||
|
musicId: T.number,
|
||||||
|
difficultId: T.number,
|
||||||
|
romVersionCode: T.number,
|
||||||
|
score: T.number,
|
||||||
|
});
|
||||||
|
|
||||||
|
export class SqlUserRecentRatingRepository
|
||||||
|
implements UserRecentRatingRepository {
|
||||||
|
constructor(private readonly _txn: Transaction) {}
|
||||||
|
|
||||||
|
async load(profileId: Id<UserDataItem>): Promise<UserRecentRatingItem[]> {
|
||||||
|
const stmt = sql
|
||||||
|
.select("music_id", "difficult_id", "rom_version_code", "score")
|
||||||
|
.from("cm_user_recent_rating")
|
||||||
|
.where("profile_id", profileId)
|
||||||
|
.order("sort_order ASC");
|
||||||
|
|
||||||
|
const rows = await this._txn.fetchRows(stmt);
|
||||||
|
|
||||||
|
return rows.map(readRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
save(
|
||||||
|
profileId: Id<UserDataItem>,
|
||||||
|
objs: UserRecentRatingItem[]
|
||||||
|
): Promise<void> {
|
||||||
|
// Don't do anything if there's nothing to save,
|
||||||
|
// since trying to execute an empty insert statement fails.
|
||||||
|
if (objs.length === 0) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
const stmt = sql
|
||||||
|
.insert(
|
||||||
|
"cm_user_recent_rating",
|
||||||
|
objs.map((obj, idx) => {
|
||||||
|
return {
|
||||||
|
id: this._txn.generateId(),
|
||||||
|
profile_id: profileId,
|
||||||
|
sort_order: idx + 1,
|
||||||
|
...writeRow(obj),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.onConflict("profile_id", "sort_order")
|
||||||
|
.doUpdate(colNames);
|
||||||
|
|
||||||
|
return this._txn.modify(stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/chunithm/static/charge.ts
Normal file
17
src/chunithm/static/charge.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
export const CHARGE_IDS = Object.freeze([
|
||||||
|
{
|
||||||
|
id: 2310, //World's End ticket x5
|
||||||
|
price: 1,
|
||||||
|
salePrice: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2060, //4x bonus ticket
|
||||||
|
price: 1,
|
||||||
|
salePrice: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2230, //6 songs premium ticket
|
||||||
|
price: 2,
|
||||||
|
salePrice: 2,
|
||||||
|
},
|
||||||
|
]);
|
||||||
854
src/chunithm/static/event.ts
Normal file
854
src/chunithm/static/event.ts
Normal file
|
|
@ -0,0 +1,854 @@
|
||||||
|
export const EVENT_IDS = Object.freeze([
|
||||||
|
// A000
|
||||||
|
0,
|
||||||
|
1506,
|
||||||
|
1604,
|
||||||
|
1702,
|
||||||
|
1953,
|
||||||
|
2206,
|
||||||
|
2353,
|
||||||
|
2553,
|
||||||
|
2804,
|
||||||
|
2999,
|
||||||
|
3000,
|
||||||
|
3001,
|
||||||
|
3002,
|
||||||
|
3003,
|
||||||
|
3006,
|
||||||
|
3007,
|
||||||
|
3008,
|
||||||
|
3009,
|
||||||
|
3010,
|
||||||
|
3011,
|
||||||
|
3012,
|
||||||
|
3013,
|
||||||
|
3014,
|
||||||
|
3015,
|
||||||
|
3016,
|
||||||
|
3017,
|
||||||
|
3018,
|
||||||
|
3019,
|
||||||
|
3020,
|
||||||
|
3021,
|
||||||
|
3026,
|
||||||
|
3027,
|
||||||
|
20003,
|
||||||
|
|
||||||
|
// A014
|
||||||
|
3004,
|
||||||
|
3005,
|
||||||
|
3022,
|
||||||
|
3023,
|
||||||
|
3024,
|
||||||
|
3025,
|
||||||
|
3028,
|
||||||
|
3029,
|
||||||
|
3030,
|
||||||
|
3031,
|
||||||
|
3032,
|
||||||
|
3100,
|
||||||
|
3101,
|
||||||
|
3102,
|
||||||
|
3103,
|
||||||
|
3104,
|
||||||
|
3150,
|
||||||
|
3151,
|
||||||
|
3152,
|
||||||
|
3153,
|
||||||
|
3154,
|
||||||
|
3155,
|
||||||
|
3156,
|
||||||
|
3157,
|
||||||
|
3158,
|
||||||
|
3159,
|
||||||
|
3160,
|
||||||
|
3161,
|
||||||
|
3162,
|
||||||
|
3163,
|
||||||
|
3164,
|
||||||
|
3165,
|
||||||
|
3166,
|
||||||
|
3167,
|
||||||
|
3168,
|
||||||
|
3169,
|
||||||
|
3200,
|
||||||
|
3201,
|
||||||
|
3202,
|
||||||
|
3203,
|
||||||
|
3204,
|
||||||
|
3205,
|
||||||
|
3206,
|
||||||
|
3207,
|
||||||
|
3208,
|
||||||
|
3209,
|
||||||
|
3210,
|
||||||
|
3211,
|
||||||
|
3212,
|
||||||
|
3213,
|
||||||
|
3214,
|
||||||
|
3215,
|
||||||
|
3216,
|
||||||
|
3217,
|
||||||
|
3218,
|
||||||
|
3219,
|
||||||
|
3250,
|
||||||
|
3251,
|
||||||
|
3252,
|
||||||
|
3253,
|
||||||
|
3254,
|
||||||
|
3255,
|
||||||
|
3256,
|
||||||
|
3257,
|
||||||
|
3258,
|
||||||
|
3259,
|
||||||
|
3260,
|
||||||
|
3261,
|
||||||
|
3262,
|
||||||
|
3263,
|
||||||
|
3300,
|
||||||
|
3301,
|
||||||
|
3302,
|
||||||
|
3303,
|
||||||
|
3304,
|
||||||
|
3305,
|
||||||
|
3306,
|
||||||
|
3307,
|
||||||
|
3308,
|
||||||
|
3309,
|
||||||
|
3310,
|
||||||
|
3311,
|
||||||
|
3350,
|
||||||
|
3351,
|
||||||
|
3352,
|
||||||
|
3353,
|
||||||
|
3400,
|
||||||
|
3401,
|
||||||
|
3402,
|
||||||
|
3403,
|
||||||
|
3404,
|
||||||
|
3405,
|
||||||
|
3406,
|
||||||
|
3407,
|
||||||
|
3408,
|
||||||
|
3409,
|
||||||
|
3410,
|
||||||
|
3411,
|
||||||
|
3412,
|
||||||
|
3413,
|
||||||
|
3414,
|
||||||
|
3415,
|
||||||
|
3416,
|
||||||
|
3417,
|
||||||
|
3450,
|
||||||
|
3451,
|
||||||
|
3452,
|
||||||
|
3453,
|
||||||
|
3454,
|
||||||
|
3455,
|
||||||
|
|
||||||
|
// A017
|
||||||
|
3500,
|
||||||
|
3501,
|
||||||
|
3502,
|
||||||
|
3503,
|
||||||
|
3504,
|
||||||
|
3505,
|
||||||
|
3506,
|
||||||
|
3507,
|
||||||
|
3508,
|
||||||
|
3509,
|
||||||
|
3510,
|
||||||
|
3511,
|
||||||
|
3512,
|
||||||
|
3513,
|
||||||
|
3514,
|
||||||
|
3515,
|
||||||
|
3550,
|
||||||
|
3551,
|
||||||
|
3552,
|
||||||
|
3553,
|
||||||
|
3554,
|
||||||
|
3555,
|
||||||
|
3556,
|
||||||
|
3557,
|
||||||
|
3580,
|
||||||
|
3581,
|
||||||
|
|
||||||
|
// Other event IDs for Chunithm Amazon
|
||||||
|
1,
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
100,
|
||||||
|
101,
|
||||||
|
102,
|
||||||
|
103,
|
||||||
|
200,
|
||||||
|
201,
|
||||||
|
202,
|
||||||
|
203,
|
||||||
|
300,
|
||||||
|
301,
|
||||||
|
400,
|
||||||
|
401,
|
||||||
|
500,
|
||||||
|
501,
|
||||||
|
700,
|
||||||
|
701,
|
||||||
|
810,
|
||||||
|
820,
|
||||||
|
821,
|
||||||
|
830,
|
||||||
|
831,
|
||||||
|
832,
|
||||||
|
834,
|
||||||
|
835,
|
||||||
|
840,
|
||||||
|
841,
|
||||||
|
842,
|
||||||
|
843,
|
||||||
|
850,
|
||||||
|
851,
|
||||||
|
852,
|
||||||
|
853,
|
||||||
|
860,
|
||||||
|
861,
|
||||||
|
862,
|
||||||
|
863,
|
||||||
|
864,
|
||||||
|
865,
|
||||||
|
870,
|
||||||
|
871,
|
||||||
|
872,
|
||||||
|
873,
|
||||||
|
880,
|
||||||
|
890,
|
||||||
|
891,
|
||||||
|
900,
|
||||||
|
901,
|
||||||
|
902,
|
||||||
|
903,
|
||||||
|
904,
|
||||||
|
905,
|
||||||
|
906,
|
||||||
|
907,
|
||||||
|
908,
|
||||||
|
909,
|
||||||
|
910,
|
||||||
|
911,
|
||||||
|
912,
|
||||||
|
913,
|
||||||
|
914,
|
||||||
|
915,
|
||||||
|
916,
|
||||||
|
917,
|
||||||
|
918,
|
||||||
|
930,
|
||||||
|
931,
|
||||||
|
932,
|
||||||
|
933,
|
||||||
|
934,
|
||||||
|
935,
|
||||||
|
936,
|
||||||
|
937,
|
||||||
|
938,
|
||||||
|
939,
|
||||||
|
940,
|
||||||
|
941,
|
||||||
|
942,
|
||||||
|
943,
|
||||||
|
944,
|
||||||
|
945,
|
||||||
|
946,
|
||||||
|
947,
|
||||||
|
960,
|
||||||
|
961,
|
||||||
|
962,
|
||||||
|
963,
|
||||||
|
964,
|
||||||
|
965,
|
||||||
|
966,
|
||||||
|
967,
|
||||||
|
968,
|
||||||
|
969,
|
||||||
|
990,
|
||||||
|
991,
|
||||||
|
992,
|
||||||
|
993,
|
||||||
|
994,
|
||||||
|
995,
|
||||||
|
996,
|
||||||
|
997,
|
||||||
|
998,
|
||||||
|
999,
|
||||||
|
1000,
|
||||||
|
1001,
|
||||||
|
1002,
|
||||||
|
1003,
|
||||||
|
1004,
|
||||||
|
1005,
|
||||||
|
1006,
|
||||||
|
1020,
|
||||||
|
1021,
|
||||||
|
1022,
|
||||||
|
1023,
|
||||||
|
1024,
|
||||||
|
1025,
|
||||||
|
1026,
|
||||||
|
1027,
|
||||||
|
1028,
|
||||||
|
1029,
|
||||||
|
1030,
|
||||||
|
1031,
|
||||||
|
1050,
|
||||||
|
1051,
|
||||||
|
1052,
|
||||||
|
1053,
|
||||||
|
1054,
|
||||||
|
1055,
|
||||||
|
1056,
|
||||||
|
1057,
|
||||||
|
1058,
|
||||||
|
1059,
|
||||||
|
1060,
|
||||||
|
1061,
|
||||||
|
1070,
|
||||||
|
1098,
|
||||||
|
1099,
|
||||||
|
1100,
|
||||||
|
1101,
|
||||||
|
1102,
|
||||||
|
1103,
|
||||||
|
1104,
|
||||||
|
1105,
|
||||||
|
1106,
|
||||||
|
1107,
|
||||||
|
1108,
|
||||||
|
1109,
|
||||||
|
1110,
|
||||||
|
1111,
|
||||||
|
1112,
|
||||||
|
1113,
|
||||||
|
1114,
|
||||||
|
1130,
|
||||||
|
1131,
|
||||||
|
1132,
|
||||||
|
1133,
|
||||||
|
1134,
|
||||||
|
1135,
|
||||||
|
1136,
|
||||||
|
1137,
|
||||||
|
1138,
|
||||||
|
1159,
|
||||||
|
1160,
|
||||||
|
1161,
|
||||||
|
1162,
|
||||||
|
1163,
|
||||||
|
1164,
|
||||||
|
1165,
|
||||||
|
1166,
|
||||||
|
1167,
|
||||||
|
1168,
|
||||||
|
1169,
|
||||||
|
1170,
|
||||||
|
1171,
|
||||||
|
1172,
|
||||||
|
1173,
|
||||||
|
1174,
|
||||||
|
1175,
|
||||||
|
1190,
|
||||||
|
1191,
|
||||||
|
1192,
|
||||||
|
1193,
|
||||||
|
1194,
|
||||||
|
1195,
|
||||||
|
1196,
|
||||||
|
1197,
|
||||||
|
1198,
|
||||||
|
1199,
|
||||||
|
1200,
|
||||||
|
1201,
|
||||||
|
1220,
|
||||||
|
1221,
|
||||||
|
1222,
|
||||||
|
1223,
|
||||||
|
1224,
|
||||||
|
1225,
|
||||||
|
1226,
|
||||||
|
1227,
|
||||||
|
1228,
|
||||||
|
1229,
|
||||||
|
1230,
|
||||||
|
1231,
|
||||||
|
1232,
|
||||||
|
1233,
|
||||||
|
1250,
|
||||||
|
1251,
|
||||||
|
1252,
|
||||||
|
1253,
|
||||||
|
1254,
|
||||||
|
1255,
|
||||||
|
1256,
|
||||||
|
1257,
|
||||||
|
1258,
|
||||||
|
1259,
|
||||||
|
1260,
|
||||||
|
1261,
|
||||||
|
1279,
|
||||||
|
1280,
|
||||||
|
1281,
|
||||||
|
1282,
|
||||||
|
1283,
|
||||||
|
1284,
|
||||||
|
1285,
|
||||||
|
1286,
|
||||||
|
1287,
|
||||||
|
1288,
|
||||||
|
1289,
|
||||||
|
1290,
|
||||||
|
1291,
|
||||||
|
1292,
|
||||||
|
1293,
|
||||||
|
1294,
|
||||||
|
1295,
|
||||||
|
1296,
|
||||||
|
1297,
|
||||||
|
1298,
|
||||||
|
1299,
|
||||||
|
1310,
|
||||||
|
1311,
|
||||||
|
1312,
|
||||||
|
1313,
|
||||||
|
1314,
|
||||||
|
1315,
|
||||||
|
1316,
|
||||||
|
1317,
|
||||||
|
1318,
|
||||||
|
1319,
|
||||||
|
1320,
|
||||||
|
1321,
|
||||||
|
1322,
|
||||||
|
1323,
|
||||||
|
1324,
|
||||||
|
1325,
|
||||||
|
1326,
|
||||||
|
1327,
|
||||||
|
1328,
|
||||||
|
1329,
|
||||||
|
1340,
|
||||||
|
1341,
|
||||||
|
1342,
|
||||||
|
1343,
|
||||||
|
1344,
|
||||||
|
1345,
|
||||||
|
1346,
|
||||||
|
1347,
|
||||||
|
1348,
|
||||||
|
1349,
|
||||||
|
1350,
|
||||||
|
1351,
|
||||||
|
1352,
|
||||||
|
1353,
|
||||||
|
1354,
|
||||||
|
1355,
|
||||||
|
1356,
|
||||||
|
1357,
|
||||||
|
1370,
|
||||||
|
1371,
|
||||||
|
1372,
|
||||||
|
1373,
|
||||||
|
1374,
|
||||||
|
1375,
|
||||||
|
1376,
|
||||||
|
1377,
|
||||||
|
1378,
|
||||||
|
1379,
|
||||||
|
1380,
|
||||||
|
1381,
|
||||||
|
1382,
|
||||||
|
1383,
|
||||||
|
1384,
|
||||||
|
1385,
|
||||||
|
1386,
|
||||||
|
1387,
|
||||||
|
1388,
|
||||||
|
1400,
|
||||||
|
1401,
|
||||||
|
1402,
|
||||||
|
1403,
|
||||||
|
1404,
|
||||||
|
1405,
|
||||||
|
1406,
|
||||||
|
1407,
|
||||||
|
1408,
|
||||||
|
1409,
|
||||||
|
1410,
|
||||||
|
1411,
|
||||||
|
1412,
|
||||||
|
1413,
|
||||||
|
1414,
|
||||||
|
1415,
|
||||||
|
1416,
|
||||||
|
1430,
|
||||||
|
1431,
|
||||||
|
1432,
|
||||||
|
1433,
|
||||||
|
1434,
|
||||||
|
1435,
|
||||||
|
1436,
|
||||||
|
1437,
|
||||||
|
1438,
|
||||||
|
1439,
|
||||||
|
1440,
|
||||||
|
1441,
|
||||||
|
1442,
|
||||||
|
1443,
|
||||||
|
1444,
|
||||||
|
1445,
|
||||||
|
1446,
|
||||||
|
1447,
|
||||||
|
1460,
|
||||||
|
1461,
|
||||||
|
1462,
|
||||||
|
1463,
|
||||||
|
1464,
|
||||||
|
1465,
|
||||||
|
1500,
|
||||||
|
1501,
|
||||||
|
1502,
|
||||||
|
1504,
|
||||||
|
1505,
|
||||||
|
1507,
|
||||||
|
1508,
|
||||||
|
1509,
|
||||||
|
1510,
|
||||||
|
1511,
|
||||||
|
1512,
|
||||||
|
1513,
|
||||||
|
1514,
|
||||||
|
1515,
|
||||||
|
1516,
|
||||||
|
1517,
|
||||||
|
1518,
|
||||||
|
1519,
|
||||||
|
1520,
|
||||||
|
1521,
|
||||||
|
1522,
|
||||||
|
1523,
|
||||||
|
1524,
|
||||||
|
1600,
|
||||||
|
1601,
|
||||||
|
1603,
|
||||||
|
1605,
|
||||||
|
1606,
|
||||||
|
1650,
|
||||||
|
1651,
|
||||||
|
1652,
|
||||||
|
1653,
|
||||||
|
1654,
|
||||||
|
1655,
|
||||||
|
1656,
|
||||||
|
1657,
|
||||||
|
1658,
|
||||||
|
1659,
|
||||||
|
1660,
|
||||||
|
1661,
|
||||||
|
1700,
|
||||||
|
1701,
|
||||||
|
1703,
|
||||||
|
1750,
|
||||||
|
1751,
|
||||||
|
1752,
|
||||||
|
1753,
|
||||||
|
1754,
|
||||||
|
1755,
|
||||||
|
1756,
|
||||||
|
1757,
|
||||||
|
1758,
|
||||||
|
1759,
|
||||||
|
1760,
|
||||||
|
1761,
|
||||||
|
1762,
|
||||||
|
1763,
|
||||||
|
1764,
|
||||||
|
1765,
|
||||||
|
1766,
|
||||||
|
1767,
|
||||||
|
1768,
|
||||||
|
1769,
|
||||||
|
1770,
|
||||||
|
1800,
|
||||||
|
1801,
|
||||||
|
1802,
|
||||||
|
1803,
|
||||||
|
1804,
|
||||||
|
1805,
|
||||||
|
1850,
|
||||||
|
1851,
|
||||||
|
1852,
|
||||||
|
1853,
|
||||||
|
1854,
|
||||||
|
1855,
|
||||||
|
1856,
|
||||||
|
1857,
|
||||||
|
1880,
|
||||||
|
1881,
|
||||||
|
1882,
|
||||||
|
1883,
|
||||||
|
1884,
|
||||||
|
1885,
|
||||||
|
1886,
|
||||||
|
1887,
|
||||||
|
1888,
|
||||||
|
1889,
|
||||||
|
1890,
|
||||||
|
1900,
|
||||||
|
1901,
|
||||||
|
1903,
|
||||||
|
1904,
|
||||||
|
1905,
|
||||||
|
1906,
|
||||||
|
1907,
|
||||||
|
1908,
|
||||||
|
1909,
|
||||||
|
1910,
|
||||||
|
1911,
|
||||||
|
1912,
|
||||||
|
1913,
|
||||||
|
1950,
|
||||||
|
1951,
|
||||||
|
1952,
|
||||||
|
1954,
|
||||||
|
1955,
|
||||||
|
1956,
|
||||||
|
2000,
|
||||||
|
2001,
|
||||||
|
2003,
|
||||||
|
2004,
|
||||||
|
2005,
|
||||||
|
2006,
|
||||||
|
2007,
|
||||||
|
2008,
|
||||||
|
2009,
|
||||||
|
2010,
|
||||||
|
2011,
|
||||||
|
2012,
|
||||||
|
2013,
|
||||||
|
2050,
|
||||||
|
2051,
|
||||||
|
2052,
|
||||||
|
2053,
|
||||||
|
2054,
|
||||||
|
2055,
|
||||||
|
2056,
|
||||||
|
2100,
|
||||||
|
2101,
|
||||||
|
2103,
|
||||||
|
2104,
|
||||||
|
2105,
|
||||||
|
2106,
|
||||||
|
2107,
|
||||||
|
2108,
|
||||||
|
2109,
|
||||||
|
2110,
|
||||||
|
2111,
|
||||||
|
2150,
|
||||||
|
2151,
|
||||||
|
2152,
|
||||||
|
2153,
|
||||||
|
2154,
|
||||||
|
2155,
|
||||||
|
2156,
|
||||||
|
2200,
|
||||||
|
2201,
|
||||||
|
2202,
|
||||||
|
2203,
|
||||||
|
2204,
|
||||||
|
2205,
|
||||||
|
2207,
|
||||||
|
2208,
|
||||||
|
2209,
|
||||||
|
2210,
|
||||||
|
2211,
|
||||||
|
2212,
|
||||||
|
2213,
|
||||||
|
2214,
|
||||||
|
2215,
|
||||||
|
2216,
|
||||||
|
2217,
|
||||||
|
2218,
|
||||||
|
2219,
|
||||||
|
2220,
|
||||||
|
2221,
|
||||||
|
2222,
|
||||||
|
2223,
|
||||||
|
2224,
|
||||||
|
2225,
|
||||||
|
2226,
|
||||||
|
2250,
|
||||||
|
2251,
|
||||||
|
2252,
|
||||||
|
2253,
|
||||||
|
2280,
|
||||||
|
2281,
|
||||||
|
2282,
|
||||||
|
2283,
|
||||||
|
2300,
|
||||||
|
2301,
|
||||||
|
2302,
|
||||||
|
2303,
|
||||||
|
2304,
|
||||||
|
2305,
|
||||||
|
2306,
|
||||||
|
2307,
|
||||||
|
2308,
|
||||||
|
2350,
|
||||||
|
2351,
|
||||||
|
2352,
|
||||||
|
2354,
|
||||||
|
2355,
|
||||||
|
2356,
|
||||||
|
2380,
|
||||||
|
2381,
|
||||||
|
2382,
|
||||||
|
2383,
|
||||||
|
2400,
|
||||||
|
2401,
|
||||||
|
2402,
|
||||||
|
2403,
|
||||||
|
2404,
|
||||||
|
2405,
|
||||||
|
2406,
|
||||||
|
2407,
|
||||||
|
2408,
|
||||||
|
2409,
|
||||||
|
2410,
|
||||||
|
2411,
|
||||||
|
2412,
|
||||||
|
2413,
|
||||||
|
2414,
|
||||||
|
2415,
|
||||||
|
2416,
|
||||||
|
2417,
|
||||||
|
2418,
|
||||||
|
2419,
|
||||||
|
2420,
|
||||||
|
2421,
|
||||||
|
2450,
|
||||||
|
2451,
|
||||||
|
2452,
|
||||||
|
2453,
|
||||||
|
2454,
|
||||||
|
2455,
|
||||||
|
2456,
|
||||||
|
2457,
|
||||||
|
2458,
|
||||||
|
2459,
|
||||||
|
2500,
|
||||||
|
2501,
|
||||||
|
2502,
|
||||||
|
2503,
|
||||||
|
2504,
|
||||||
|
2505,
|
||||||
|
2506,
|
||||||
|
2507,
|
||||||
|
2508,
|
||||||
|
2509,
|
||||||
|
2510,
|
||||||
|
2511,
|
||||||
|
2550,
|
||||||
|
2551,
|
||||||
|
2552,
|
||||||
|
2554,
|
||||||
|
2555,
|
||||||
|
2556,
|
||||||
|
2557,
|
||||||
|
2558,
|
||||||
|
2559,
|
||||||
|
2560,
|
||||||
|
2561,
|
||||||
|
2562,
|
||||||
|
2600,
|
||||||
|
2601,
|
||||||
|
2602,
|
||||||
|
2603,
|
||||||
|
2604,
|
||||||
|
2605,
|
||||||
|
2606,
|
||||||
|
2607,
|
||||||
|
2608,
|
||||||
|
2609,
|
||||||
|
2610,
|
||||||
|
2611,
|
||||||
|
2612,
|
||||||
|
2613,
|
||||||
|
2614,
|
||||||
|
2615,
|
||||||
|
2650,
|
||||||
|
2651,
|
||||||
|
2652,
|
||||||
|
2653,
|
||||||
|
2654,
|
||||||
|
2655,
|
||||||
|
2656,
|
||||||
|
2657,
|
||||||
|
2658,
|
||||||
|
2659,
|
||||||
|
2660,
|
||||||
|
2700,
|
||||||
|
2701,
|
||||||
|
2702,
|
||||||
|
2703,
|
||||||
|
2704,
|
||||||
|
2705,
|
||||||
|
2706,
|
||||||
|
2707,
|
||||||
|
2708,
|
||||||
|
2709,
|
||||||
|
2710,
|
||||||
|
2711,
|
||||||
|
2750,
|
||||||
|
2751,
|
||||||
|
2752,
|
||||||
|
2753,
|
||||||
|
2754,
|
||||||
|
2755,
|
||||||
|
2800,
|
||||||
|
2801,
|
||||||
|
2802,
|
||||||
|
2803,
|
||||||
|
2805,
|
||||||
|
2806,
|
||||||
|
2807,
|
||||||
|
2808,
|
||||||
|
2809,
|
||||||
|
2810,
|
||||||
|
2811,
|
||||||
|
2812,
|
||||||
|
2813,
|
||||||
|
2814,
|
||||||
|
2815,
|
||||||
|
2816,
|
||||||
|
2817,
|
||||||
|
2818,
|
||||||
|
2850,
|
||||||
|
2851,
|
||||||
|
2852,
|
||||||
|
2853,
|
||||||
|
2854,
|
||||||
|
2855,
|
||||||
|
2856,
|
||||||
|
2857,
|
||||||
|
2900,
|
||||||
|
2901,
|
||||||
|
2902,
|
||||||
|
2903,
|
||||||
|
2950,
|
||||||
|
2951,
|
||||||
|
2952,
|
||||||
|
2953,
|
||||||
|
8001,
|
||||||
|
20000,
|
||||||
|
20001,
|
||||||
|
20002,
|
||||||
|
20004,
|
||||||
|
]);
|
||||||
43
src/idz/common/aes.ts
Normal file
43
src/idz/common/aes.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { Decipher, Cipher, createCipheriv, createDecipheriv } from "crypto";
|
||||||
|
|
||||||
|
import { ByteStream } from "../../util/stream";
|
||||||
|
|
||||||
|
export const BLOCK_SIZE = 16;
|
||||||
|
|
||||||
|
export default class AesEcbStream implements ByteStream {
|
||||||
|
private readonly _dec: Decipher;
|
||||||
|
private readonly _enc: Cipher;
|
||||||
|
|
||||||
|
constructor(private _stm: ByteStream, keybuf: Buffer) {
|
||||||
|
this._dec = createDecipheriv("aes-128-ecb", keybuf, null).setAutoPadding(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
this._enc = createCipheriv("aes-128-ecb", keybuf, null).setAutoPadding(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this._stm.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
async read(nbytes: number): Promise<Buffer> {
|
||||||
|
if (nbytes % BLOCK_SIZE !== 0) {
|
||||||
|
throw new Error("Attempted to read partial cipher block");
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphertext = await this._stm.read(nbytes);
|
||||||
|
|
||||||
|
return this._dec.update(ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
async write(buf: Buffer): Promise<void> {
|
||||||
|
if (buf.length % BLOCK_SIZE !== 0) {
|
||||||
|
throw new Error("Attempted to write partial cipher block");
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphertext = this._enc.update(buf);
|
||||||
|
|
||||||
|
await this._stm.write(ciphertext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,17 @@
|
||||||
export function byteString(n: bigint, length: number) {
|
export function readBigInt(buf: Buffer) {
|
||||||
|
let result = 0n;
|
||||||
|
|
||||||
|
for (let i = 0; i < buf.length; i++) {
|
||||||
|
const shift = 8n * BigInt(i);
|
||||||
|
const byte = buf.readUInt8(i);
|
||||||
|
|
||||||
|
result |= BigInt(byte) << shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeBigInt(n: bigint, length: number) {
|
||||||
const result = Buffer.alloc(length);
|
const result = Buffer.alloc(length);
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
5
src/idz/common/index.ts
Normal file
5
src/idz/common/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import setup from "./setup";
|
||||||
|
|
||||||
|
export { BLOCK_SIZE } from "./aes";
|
||||||
|
export { ClientHello } from "./setup";
|
||||||
|
export default setup;
|
||||||
87
src/idz/common/setup.ts
Normal file
87
src/idz/common/setup.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { Socket } from "net";
|
||||||
|
|
||||||
|
import AesEcbStream from "./aes";
|
||||||
|
import { readBigInt, modPow, writeBigInt } from "./bigint";
|
||||||
|
import IoByteStream, { ByteStream } from "../../util/stream";
|
||||||
|
|
||||||
|
interface RsaKey {
|
||||||
|
N: bigint;
|
||||||
|
d: bigint;
|
||||||
|
e: bigint;
|
||||||
|
hashN: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientHello {
|
||||||
|
pcbId: string;
|
||||||
|
protocol: string;
|
||||||
|
model: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IdzConnection {
|
||||||
|
aesStream: ByteStream;
|
||||||
|
clientHello: ClientHello;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proof-of-concept, so we only ever use one of the ten RSA key pairs
|
||||||
|
const rsaKey = {
|
||||||
|
N: 4922323266120814292574970172377860734034664704992758249880018618131907367614177800329506877981986877921220485681998287752778495334541127048495486311792061n,
|
||||||
|
d: 1163847742215766215216916151663017691387519688859977157498780867776436010396072628219119707788340687440419444081289736279466637153082223960965411473296473n,
|
||||||
|
e: 3961365081960959178294197133768419551060435043430437330799371731939550352626564261219865471710058480523874787120718634318364066605378505537556570049131337n,
|
||||||
|
hashN: 2662304617,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Proof-of-concept, so we only use one fixed session key
|
||||||
|
const aesKey = Buffer.from("ffddeeccbbaa99887766554433221100", "hex");
|
||||||
|
|
||||||
|
function writeServerHello(aesKey: Buffer, rsaKey: RsaKey): Buffer {
|
||||||
|
const M = readBigInt(aesKey);
|
||||||
|
const keyEnc = modPow(M, rsaKey.e, rsaKey.N);
|
||||||
|
const result = Buffer.alloc(0x48);
|
||||||
|
|
||||||
|
result.set(writeBigInt(keyEnc, 0x40), 0x00);
|
||||||
|
result.writeUInt32LE(0x01020304, 0x40); // Meaning is unknown
|
||||||
|
result.writeUInt32LE(rsaKey.hashN, 0x44);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readClientHello(buf: Buffer) {
|
||||||
|
const magic = buf.readUInt32LE(0x00);
|
||||||
|
|
||||||
|
if (magic === 0xFE78571D) {
|
||||||
|
throw new Error(
|
||||||
|
"Server Box data, ignore."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (magic !== 0x01020304) {
|
||||||
|
throw new Error(
|
||||||
|
"Invalid magic number, cryptographic processing probably incorrect."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
pcbId: buf.slice(0x04, 0x0f).toString("ascii"),
|
||||||
|
protocol: buf.slice(0x10, 0x13).toString("ascii"),
|
||||||
|
model: buf.slice(0x18, 0x0c).toString("ascii"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function setup(socket: Socket): Promise<IdzConnection> {
|
||||||
|
const tcpStream = new IoByteStream(socket);
|
||||||
|
const serverHello = writeServerHello(aesKey, rsaKey);
|
||||||
|
|
||||||
|
await tcpStream.write(serverHello);
|
||||||
|
|
||||||
|
const aesStream = new AesEcbStream(tcpStream, aesKey);
|
||||||
|
|
||||||
|
const response = await aesStream.read(0x30);
|
||||||
|
|
||||||
|
if (response.length !== 0x30) {
|
||||||
|
throw new Error("Truncated client hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientHello = readClientHello(response);
|
||||||
|
|
||||||
|
return { aesStream, clientHello };
|
||||||
|
}
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { CheckTeamNameRequest } from "../request/checkTeamName";
|
|
||||||
|
|
||||||
checkTeamName.msgCode = 0x00a2;
|
|
||||||
checkTeamName.msgLen = 0x0040;
|
|
||||||
|
|
||||||
export function checkTeamName(buf: Buffer): CheckTeamNameRequest {
|
|
||||||
return { type: "check_team_name_req" };
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import { CreateAutoTeamRequest } from "../request/createAutoTeam";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
createAutoTeam.msgCode = 0x007b;
|
|
||||||
createAutoTeam.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function createAutoTeam(buf: Buffer): CreateAutoTeamRequest {
|
|
||||||
return {
|
|
||||||
type: "create_auto_team_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
field_0008: buf.readUInt32LE(0x0008),
|
|
||||||
field_000C: buf.readUInt8(0x000c),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import iconv from "iconv-lite";
|
|
||||||
|
|
||||||
import { car } from "./_car";
|
|
||||||
import { chara } from "./_chara";
|
|
||||||
import { CreateProfileRequest } from "../request/createProfile";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
createProfile.msgCode = 0x0066;
|
|
||||||
createProfile.msgLen = 0x00c0;
|
|
||||||
|
|
||||||
export function createProfile(buf: Buffer): CreateProfileRequest {
|
|
||||||
return {
|
|
||||||
type: "create_profile_req",
|
|
||||||
aimeId: buf.readInt32LE(0x0004) as AimeId,
|
|
||||||
luid: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
|
||||||
name: iconv.decode(
|
|
||||||
buf.slice(0x001e, buf.indexOf("\0", 0x001e)),
|
|
||||||
"shift_jis"
|
|
||||||
),
|
|
||||||
field_0034: buf.readUInt32LE(0x0034),
|
|
||||||
car: car(buf.slice(0x0040, 0x00a0)),
|
|
||||||
chara: chara(buf.slice(0x00a0, 0x00b4)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import iconv from "iconv-lite";
|
|
||||||
|
|
||||||
import { CreateTeamRequest } from "../request/createTeam";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
createTeam.msgCode = 0x0071;
|
|
||||||
createTeam.msgLen = 0x0050;
|
|
||||||
|
|
||||||
export function createTeam(buf: Buffer): CreateTeamRequest {
|
|
||||||
return {
|
|
||||||
type: "create_team_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
teamName: iconv.decode(
|
|
||||||
buf.slice(0x0008, buf.indexOf("\0", 0x0008)),
|
|
||||||
"shift_jis"
|
|
||||||
),
|
|
||||||
field_0028: buf.readUInt16LE(0x0028),
|
|
||||||
field_002C: buf.readUInt32LE(0x002c),
|
|
||||||
nameBg: buf.readUInt8(0x0030),
|
|
||||||
field_0032: buf.readUInt16LE(0x0032),
|
|
||||||
prevTeamId: buf.readUInt32LE(0x0034),
|
|
||||||
pcbId: buf.slice(0x0038, buf.indexOf("\0", 0x0038)).toString("ascii"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { DiscoverProfileRequest } from "../request/discoverProfile";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
discoverProfile.msgCode = 0x006b;
|
|
||||||
discoverProfile.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function discoverProfile(buf: Buffer): DiscoverProfileRequest {
|
|
||||||
return {
|
|
||||||
type: "discover_profile_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
import logger from "debug";
|
|
||||||
import { Transform } from "stream";
|
|
||||||
|
|
||||||
import { checkTeamName } from "./checkTeamName";
|
|
||||||
import { createProfile } from "./createProfile";
|
|
||||||
import { createTeam } from "./createTeam";
|
|
||||||
import { createAutoTeam } from "./createAutoTeam";
|
|
||||||
import { discoverProfile } from "./discoverProfile";
|
|
||||||
import { load2on2_v1, load2on2_v2 } from "./load2on2";
|
|
||||||
import { loadConfig } from "./loadConfig";
|
|
||||||
import { loadConfig2 } from "./loadConfig2";
|
|
||||||
import { loadEventInfo } from "./loadEventInfo";
|
|
||||||
import { loadGacha } from "./loadGacha";
|
|
||||||
import { loadGarage } from "./loadGarage";
|
|
||||||
import { loadGeneralReward1, loadGeneralReward2 } from "./loadGeneralReward";
|
|
||||||
import { loadGhost } from "./loadGhost";
|
|
||||||
import { loadProfile2, loadProfile3 } from "./loadProfile";
|
|
||||||
import { loadRewardTable } from "./loadRewardTable";
|
|
||||||
import { loadServerList } from "./loadServerList";
|
|
||||||
import { loadStocker } from "./loadStocker";
|
|
||||||
import { loadTeam } from "./loadTeam";
|
|
||||||
import { loadTeamRanking, loadTeamRanking2 } from "./loadTeamRanking";
|
|
||||||
import { loadTopTen1 } from "./loadTopTen1";
|
|
||||||
import { loadTopTen2 } from "./loadTopTen2";
|
|
||||||
import { lockGarage } from "./lockGarage";
|
|
||||||
import { lockProfile } from "./lockProfile";
|
|
||||||
import { msg00AD } from "./msg00AD";
|
|
||||||
import { saveExpedition1, saveExpedition2 } from "./saveExpedition";
|
|
||||||
import { saveGarage } from "./saveGarage";
|
|
||||||
import { saveNewCar } from "./saveNewCar";
|
|
||||||
import { saveProfile2 } from "./saveProfile2";
|
|
||||||
import { saveProfile3 } from "./saveProfile3";
|
|
||||||
import { saveSettings } from "./saveSettings";
|
|
||||||
import { saveStocker } from "./saveStocker";
|
|
||||||
import { saveTeamBanner } from "./saveTeamBanner";
|
|
||||||
import { saveTimeAttack1, saveTimeAttack2 } from "./saveTimeAttack";
|
|
||||||
import { saveTopic } from "./saveTopic";
|
|
||||||
import { unlockProfile } from "./unlockProfile";
|
|
||||||
import { updateProvisionalStoreRank } from "./updateProvisionalStoreRank";
|
|
||||||
import { updateTeamLeader } from "./updateTeamLeader";
|
|
||||||
import { updateTeamMember } from "./updateTeamMember";
|
|
||||||
import {
|
|
||||||
updateStoryClearNum1,
|
|
||||||
updateStoryClearNum2,
|
|
||||||
} from "./updateStoryClearNum";
|
|
||||||
import { Request } from "../request";
|
|
||||||
import { updateResult } from "./updateResult";
|
|
||||||
import { updateTeamPoints } from "./updateTeamPoints";
|
|
||||||
import { updateUiReport } from "./updateUiReport";
|
|
||||||
import { updateUserLog } from "./updateUserLog";
|
|
||||||
import { lockProfileExtend } from "./lockProfileExtend";
|
|
||||||
|
|
||||||
const debug = logger("app:idz:decoder");
|
|
||||||
|
|
||||||
export type ReaderFn = ((buf: Buffer) => Request) & {
|
|
||||||
msgCode: number;
|
|
||||||
msgLen: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const funcList: ReaderFn[] = [
|
|
||||||
checkTeamName,
|
|
||||||
createAutoTeam,
|
|
||||||
createProfile,
|
|
||||||
createTeam,
|
|
||||||
discoverProfile,
|
|
||||||
load2on2_v1,
|
|
||||||
load2on2_v2,
|
|
||||||
loadConfig,
|
|
||||||
loadConfig2,
|
|
||||||
loadEventInfo,
|
|
||||||
loadGacha,
|
|
||||||
loadGarage,
|
|
||||||
loadGeneralReward1,
|
|
||||||
loadGeneralReward2,
|
|
||||||
loadGhost,
|
|
||||||
loadProfile2,
|
|
||||||
loadProfile3,
|
|
||||||
loadRewardTable,
|
|
||||||
loadServerList,
|
|
||||||
loadStocker,
|
|
||||||
loadTeam,
|
|
||||||
loadTeamRanking,
|
|
||||||
loadTeamRanking2,
|
|
||||||
loadTopTen1,
|
|
||||||
loadTopTen2,
|
|
||||||
lockGarage,
|
|
||||||
lockProfile,
|
|
||||||
lockProfileExtend,
|
|
||||||
msg00AD,
|
|
||||||
saveExpedition1,
|
|
||||||
saveExpedition2,
|
|
||||||
saveGarage,
|
|
||||||
saveNewCar,
|
|
||||||
saveProfile2,
|
|
||||||
saveProfile3,
|
|
||||||
saveSettings,
|
|
||||||
saveStocker,
|
|
||||||
saveTeamBanner,
|
|
||||||
saveTimeAttack1,
|
|
||||||
saveTimeAttack2,
|
|
||||||
saveTopic,
|
|
||||||
unlockProfile,
|
|
||||||
updateProvisionalStoreRank,
|
|
||||||
updateResult,
|
|
||||||
updateStoryClearNum1,
|
|
||||||
updateStoryClearNum2,
|
|
||||||
updateTeamLeader,
|
|
||||||
updateTeamMember,
|
|
||||||
updateTeamPoints,
|
|
||||||
updateUiReport,
|
|
||||||
updateUserLog,
|
|
||||||
];
|
|
||||||
|
|
||||||
const readerFns = new Map<number, ReaderFn>();
|
|
||||||
const msgLengths = new Map<number, number>();
|
|
||||||
|
|
||||||
for (const fn of funcList) {
|
|
||||||
readerFns.set(fn.msgCode, fn);
|
|
||||||
msgLengths.set(fn.msgCode, fn.msgLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
function readHeader(buf: Buffer) {
|
|
||||||
return {
|
|
||||||
blah: "blah",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Decoder extends Transform {
|
|
||||||
state: Buffer;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super({
|
|
||||||
readableObjectMode: true,
|
|
||||||
writableObjectMode: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.state = Buffer.alloc(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
_transform(chunk: Buffer, encoding, callback) {
|
|
||||||
this.state = Buffer.concat([this.state, chunk]);
|
|
||||||
|
|
||||||
// Read header
|
|
||||||
|
|
||||||
if (this.state.length < 0x04) {
|
|
||||||
return callback(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const magic = this.state.readUInt32LE(0);
|
|
||||||
|
|
||||||
if (magic !== 0x01020304) {
|
|
||||||
return callback(
|
|
||||||
new Error(
|
|
||||||
"Invalid magic number, cryptographic processing probably incorrect."
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.length < 0x30) {
|
|
||||||
return callback(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const header = readHeader(this.state);
|
|
||||||
|
|
||||||
if (this.state.length < 0x32) {
|
|
||||||
return callback(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const msgCode = this.state.readUInt16LE(0x30);
|
|
||||||
const msgLen = msgLengths.get(msgCode);
|
|
||||||
|
|
||||||
if (msgLen === undefined) {
|
|
||||||
return callback(
|
|
||||||
new Error(
|
|
||||||
`Unknown command code ${msgCode.toString(16)}, cannot continue`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.length < 0x30 + msgLen) {
|
|
||||||
return callback(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const reqBuf = this.state.slice(0, 0x30 + msgLen);
|
|
||||||
const payloadBuf = reqBuf.slice(0x30);
|
|
||||||
|
|
||||||
if (debug.enabled) {
|
|
||||||
debug("Raw: %s", reqBuf.toString("hex"));
|
|
||||||
debug("Header: %j", header);
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = readerFns.get(msgCode);
|
|
||||||
|
|
||||||
if (reader === undefined) {
|
|
||||||
return callback(
|
|
||||||
new Error(`No reader for command code ${msgCode.toString(16)}`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = reader(payloadBuf);
|
|
||||||
|
|
||||||
debug("Payload: %j", payload);
|
|
||||||
|
|
||||||
return callback(null, payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { ExtId } from "../model/base";
|
|
||||||
import { Team } from "../model/team";
|
|
||||||
import { Load2on2Request1, Load2on2Request2 } from "../request/load2on2";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
load2on2_v1.msgCode = 0x00b0;
|
|
||||||
load2on2_v1.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function load2on2_v1(buf: Buffer): Load2on2Request1 {
|
|
||||||
return {
|
|
||||||
type: "load_2on2_req",
|
|
||||||
format: 1,
|
|
||||||
field_0002: buf.readUInt16LE(0x0002),
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
teamId: buf.readUInt32LE(0x0008) as ExtId<Team>,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
load2on2_v2.msgCode = 0x0132;
|
|
||||||
load2on2_v2.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function load2on2_v2(buf: Buffer): Load2on2Request2 {
|
|
||||||
return {
|
|
||||||
type: "load_2on2_req",
|
|
||||||
format: 2,
|
|
||||||
field_0002: buf.readUInt16LE(0x0002),
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
teamId: buf.readUInt32LE(0x0008) as ExtId<Team>,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { LoadConfigRequest } from "../request/loadConfig";
|
|
||||||
|
|
||||||
loadConfig.msgCode = 0x0004;
|
|
||||||
loadConfig.msgLen = 0x0050;
|
|
||||||
|
|
||||||
export function loadConfig(): LoadConfigRequest {
|
|
||||||
return { type: "load_config_req" };
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { LoadConfigRequest2 } from "../request/loadConfig2";
|
|
||||||
|
|
||||||
loadConfig2.msgCode = 0x00ab;
|
|
||||||
loadConfig2.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function loadConfig2(): LoadConfigRequest2 {
|
|
||||||
return { type: "load_config_v2_req" };
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { LoadEventInfoRequest } from "../request/loadEventInfo";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
loadEventInfo.msgCode = 0x00be;
|
|
||||||
loadEventInfo.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function loadEventInfo(buf: Buffer): LoadEventInfoRequest {
|
|
||||||
return {
|
|
||||||
type: "load_event_info_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { LoadGachaRequest } from "../request/loadGacha";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
loadGacha.msgCode = 0x00c1;
|
|
||||||
loadGacha.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function loadGacha(buf: Buffer): LoadGachaRequest {
|
|
||||||
return {
|
|
||||||
type: "load_gacha_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import { LoadGarageRequest } from "../request/loadGarage";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
loadGarage.msgCode = 0x0090;
|
|
||||||
loadGarage.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function loadGarage(buf: Buffer): LoadGarageRequest {
|
|
||||||
return {
|
|
||||||
type: "load_garage_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
fetchOffset: buf.readUInt8(0x0008),
|
|
||||||
field_000A: buf.readUInt16LE(0x000a),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import { LoadGhostRequest } from "../request/loadGhost";
|
|
||||||
|
|
||||||
loadGhost.msgCode = 0x00a0;
|
|
||||||
loadGhost.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function loadGhost(buf: Buffer): LoadGhostRequest {
|
|
||||||
return {
|
|
||||||
type: "load_ghost_req",
|
|
||||||
field_0002: buf.readUInt16LE(0x0002),
|
|
||||||
field_0004: buf.readUInt16LE(0x0004),
|
|
||||||
field_0008: buf.readUInt32LE(0x0008),
|
|
||||||
field_000C: buf.readUInt16LE(0x000c),
|
|
||||||
field_000E: buf.readUInt16LE(0x000e),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
import {
|
|
||||||
LoadProfileRequest2,
|
|
||||||
LoadProfileRequest3,
|
|
||||||
} from "../request/loadProfile";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
loadProfile2.msgCode = 0x0067;
|
|
||||||
loadProfile2.msgLen = 0x0020;
|
|
||||||
|
|
||||||
export function loadProfile2(buf: Buffer): LoadProfileRequest2 {
|
|
||||||
return {
|
|
||||||
type: "load_profile_req",
|
|
||||||
format: 2,
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
luid: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
loadProfile3.msgCode = 0x0012f;
|
|
||||||
loadProfile3.msgLen = 0x0020;
|
|
||||||
|
|
||||||
export function loadProfile3(buf: Buffer): LoadProfileRequest3 {
|
|
||||||
return {
|
|
||||||
type: "load_profile_req",
|
|
||||||
format: 3,
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
luid: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import { LoadRewardTableRequest } from "../request/loadRewardTable";
|
|
||||||
|
|
||||||
loadRewardTable.msgCode = 0x0086;
|
|
||||||
loadRewardTable.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function loadRewardTable(): LoadRewardTableRequest {
|
|
||||||
return {
|
|
||||||
type: "load_reward_table_req",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { LoadStockerRequest } from "../request/loadStocker";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
loadStocker.msgCode = 0x00a7;
|
|
||||||
loadStocker.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function loadStocker(buf: Buffer): LoadStockerRequest {
|
|
||||||
return {
|
|
||||||
type: "load_stocker_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { LoadTeamRequest } from "../request/loadTeam";
|
|
||||||
import { ExtId } from "../model/base";
|
|
||||||
import { Team } from "../model/team";
|
|
||||||
|
|
||||||
loadTeam.msgCode = 0x0077;
|
|
||||||
loadTeam.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function loadTeam(buf: Buffer): LoadTeamRequest {
|
|
||||||
const extId = buf.readUInt32LE(0x0008);
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "load_team_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004),
|
|
||||||
teamExtId: extId !== 0xffffffff ? (extId as ExtId<Team>) : undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import { LoadTeamRankingRequest } from "../request/loadTeamRanking";
|
|
||||||
|
|
||||||
loadTeamRanking.msgCode = 0x00b9;
|
|
||||||
loadTeamRanking.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function loadTeamRanking(buf: Buffer): LoadTeamRankingRequest {
|
|
||||||
return {
|
|
||||||
type: "load_team_ranking_req",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// not sure what the difference is...
|
|
||||||
|
|
||||||
loadTeamRanking2.msgCode = 0x00bb;
|
|
||||||
loadTeamRanking2.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function loadTeamRanking2(buf: Buffer): LoadTeamRankingRequest {
|
|
||||||
return {
|
|
||||||
type: "load_team_ranking_req",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import { LockGarageRequest } from "../request/lockGarage";
|
|
||||||
|
|
||||||
lockGarage.msgCode = 0x00a9;
|
|
||||||
lockGarage.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function lockGarage(buf: Buffer): LockGarageRequest {
|
|
||||||
return {
|
|
||||||
type: "lock_garage_request",
|
|
||||||
field_0004: buf.readUInt32LE(0x0004),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import { LockProfileRequest } from "../request/lockProfile";
|
|
||||||
|
|
||||||
lockProfile.msgCode = 0x0069;
|
|
||||||
lockProfile.msgLen = 0x0020;
|
|
||||||
|
|
||||||
export function lockProfile(buf: Buffer): LockProfileRequest {
|
|
||||||
return {
|
|
||||||
type: "lock_profile_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004),
|
|
||||||
pcbId: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
|
||||||
field_0018: buf.readUInt16LE(0x0018),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import { LockProfileExtendRequest } from "../request/lockProfileExtend";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
lockProfileExtend.msgCode = 0x006d;
|
|
||||||
lockProfileExtend.msgLen = 0x0020;
|
|
||||||
|
|
||||||
export function lockProfileExtend(buf: Buffer): LockProfileExtendRequest {
|
|
||||||
return {
|
|
||||||
type: "lock_profile_extend_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
luid: buf.slice(0x0008, buf.indexOf("\0")).toString("ascii"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { Message00AD } from "../request/msg00AD";
|
|
||||||
|
|
||||||
msg00AD.msgCode = 0x00ad;
|
|
||||||
msg00AD.msgLen = 0x061c;
|
|
||||||
|
|
||||||
export function msg00AD(buf: Buffer): Message00AD {
|
|
||||||
return { type: "msg_00AD_req" };
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import { car } from "./_car";
|
|
||||||
import { SaveGarageRequest } from "../request/saveGarage";
|
|
||||||
|
|
||||||
saveGarage.msgCode = 0x008e;
|
|
||||||
saveGarage.msgLen = 0x0090;
|
|
||||||
|
|
||||||
export function saveGarage(buf: Buffer): SaveGarageRequest {
|
|
||||||
const field_0068: number[] = [];
|
|
||||||
|
|
||||||
for (let offset = 0x0068; offset < 0x007c; offset += 2) {
|
|
||||||
field_0068.push(buf.readUInt16LE(offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "save_garage_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004),
|
|
||||||
payload: car(buf.slice(0x0008, 0x0068)),
|
|
||||||
field_0068,
|
|
||||||
field_0080: buf.readUInt8(0x0080),
|
|
||||||
field_0081: buf.readUInt8(0x0081) !== 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { car } from "./_car";
|
|
||||||
import { SaveNewCarRequest } from "../request/saveNewCar";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
saveNewCar.msgCode = 0x0079;
|
|
||||||
saveNewCar.msgLen = 0x0090;
|
|
||||||
|
|
||||||
export function saveNewCar(buf: Buffer): SaveNewCarRequest {
|
|
||||||
return {
|
|
||||||
type: "save_new_car_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
luid: buf.slice(0x0008, buf.indexOf(0, 0x0008)).toString("ascii"),
|
|
||||||
car: car(buf.slice(0x0020, 0x0080)),
|
|
||||||
field_0080: buf.readUInt32LE(0x0080),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import { SaveSettingsRequest } from "../request/saveSettings";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
saveSettings.msgCode = 0x00a5;
|
|
||||||
saveSettings.msgLen = 0x0020;
|
|
||||||
|
|
||||||
export function saveSettings(buf: Buffer): SaveSettingsRequest {
|
|
||||||
const pack = buf.readUInt32LE(0x000c);
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "save_settings_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
dpoint: buf.readUInt32LE(0x0008),
|
|
||||||
settings: {
|
|
||||||
music: buf.readUInt16LE(0x0002),
|
|
||||||
pack: buf.readUInt32LE(0x000c),
|
|
||||||
paperCup: buf.readUInt8(0x0011),
|
|
||||||
gauges: buf.readUInt8(0x0012),
|
|
||||||
aura: buf.readUInt8(0x0013),
|
|
||||||
},
|
|
||||||
field_0010: buf.readUInt8(0x0010),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import { bitmap } from "./_bitmap";
|
|
||||||
import { chara } from "./_chara";
|
|
||||||
import { CarSelector } from "../model/car";
|
|
||||||
import { SaveStockerRequest } from "../request/saveStocker";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
saveStocker.msgCode = 0x00a6;
|
|
||||||
saveStocker.msgLen = 0x00c0;
|
|
||||||
|
|
||||||
export function saveStocker(buf: Buffer): SaveStockerRequest {
|
|
||||||
return {
|
|
||||||
type: "save_stocker_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
|
|
||||||
backgrounds: bitmap(buf.slice(0x0008, 0x002c)),
|
|
||||||
selectedCar: buf.readUInt16LE(0x009c) as CarSelector,
|
|
||||||
chara: chara(buf.slice(0x009e, 0x00b2)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import { SaveTeamBannerRequest } from "../request/saveTeamBanner";
|
|
||||||
import { ExtId } from "../model/base";
|
|
||||||
import { Team } from "../model/team";
|
|
||||||
|
|
||||||
saveTeamBanner.msgCode = 0x0089;
|
|
||||||
saveTeamBanner.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function saveTeamBanner(buf: Buffer): SaveTeamBannerRequest {
|
|
||||||
return {
|
|
||||||
type: "save_team_banner_req",
|
|
||||||
teamExtId: buf.readUInt32LE(0x0004) as ExtId<Team>,
|
|
||||||
nameBg: buf.readUInt32LE(0x0008),
|
|
||||||
nameFx: buf.readUInt32LE(0x000c),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import { SaveTopicRequest } from "../request/saveTopic";
|
|
||||||
|
|
||||||
saveTopic.msgCode = 0x009a;
|
|
||||||
saveTopic.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function saveTopic(buf: Buffer): SaveTopicRequest {
|
|
||||||
const aimeId = buf.readUInt32LE(0x0004);
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "save_topic_req",
|
|
||||||
aimeId: aimeId !== 0xffffffff ? aimeId : undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { UnlockProfileRequest } from "../request/unlockProfile";
|
|
||||||
|
|
||||||
unlockProfile.msgCode = 0x006f;
|
|
||||||
unlockProfile.msgLen = 0x0020;
|
|
||||||
|
|
||||||
export function unlockProfile(buf: Buffer): UnlockProfileRequest {
|
|
||||||
return {
|
|
||||||
type: "unlock_profile_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004),
|
|
||||||
pcbId: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import { UpdateProvisionalStoreRankRequest } from "../request/updateProvisionalStoreRank";
|
|
||||||
|
|
||||||
updateProvisionalStoreRank.msgCode = 0x0082;
|
|
||||||
updateProvisionalStoreRank.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function updateProvisionalStoreRank(
|
|
||||||
buf: Buffer
|
|
||||||
): UpdateProvisionalStoreRankRequest {
|
|
||||||
return {
|
|
||||||
type: "update_provisional_store_rank_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import {
|
|
||||||
UpdateStoryClearNumRequest1,
|
|
||||||
UpdateStoryClearNumRequest2,
|
|
||||||
} from "../request/updateStoryClearNum";
|
|
||||||
|
|
||||||
updateStoryClearNum1.msgCode = 0x007f;
|
|
||||||
updateStoryClearNum1.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function updateStoryClearNum1(
|
|
||||||
buf: Buffer
|
|
||||||
): UpdateStoryClearNumRequest1 {
|
|
||||||
return {
|
|
||||||
type: "update_story_clear_num_req",
|
|
||||||
format: 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStoryClearNum2.msgCode = 0x013d;
|
|
||||||
updateStoryClearNum2.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function updateStoryClearNum2(
|
|
||||||
buf: Buffer
|
|
||||||
): UpdateStoryClearNumRequest2 {
|
|
||||||
return {
|
|
||||||
type: "update_story_clear_num_req",
|
|
||||||
format: 2,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { ExtId } from "../model/base";
|
|
||||||
import { Team } from "../model/team";
|
|
||||||
import { UpdateTeamLeaderRequest } from "../request/updateTeamLeader";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
updateTeamLeader.msgCode = 0x008a;
|
|
||||||
updateTeamLeader.msgLen = 0x0020;
|
|
||||||
|
|
||||||
export function updateTeamLeader(buf: Buffer): UpdateTeamLeaderRequest {
|
|
||||||
return {
|
|
||||||
type: "update_team_leader_req",
|
|
||||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
|
||||||
teamExtId: buf.readUInt32LE(0x0008) as ExtId<Team>,
|
|
||||||
field_000C: buf.slice(0x000c, buf.indexOf("\0", 0x000c)).toString("ascii"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { ExtId } from "../model/base";
|
|
||||||
import { Team } from "../model/team";
|
|
||||||
import { UpdateTeamMemberRequest } from "../request/updateTeamMember";
|
|
||||||
import { AimeId } from "../../model";
|
|
||||||
|
|
||||||
updateTeamMember.msgCode = 0x0073;
|
|
||||||
updateTeamMember.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function updateTeamMember(buf: Buffer): UpdateTeamMemberRequest {
|
|
||||||
return {
|
|
||||||
type: "update_team_member_req",
|
|
||||||
action: buf.readUInt8(0x0004) === 0 ? "add" : "remove",
|
|
||||||
aimeId: buf.readUInt32LE(0x0008) as AimeId,
|
|
||||||
teamExtId: buf.readUInt32LE(0x000c) as ExtId<Team>,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { UpdateTeamPointsRequest } from "../request/updateTeamPoints";
|
|
||||||
|
|
||||||
updateTeamPoints.msgCode = 0x0081;
|
|
||||||
updateTeamPoints.msgLen = 0x0010;
|
|
||||||
|
|
||||||
export function updateTeamPoints(buf: Buffer): UpdateTeamPointsRequest {
|
|
||||||
return {
|
|
||||||
type: "update_team_points_req",
|
|
||||||
field_0004: buf.readUInt32LE(0x0004),
|
|
||||||
field_0008: buf.readUInt32LE(0x0008),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { UpdateUiReportRequest } from "../request/updateUiReport";
|
|
||||||
|
|
||||||
updateUiReport.msgCode = 0x0084;
|
|
||||||
updateUiReport.msgLen = 0x0410;
|
|
||||||
|
|
||||||
export function updateUiReport(buf: Buffer): UpdateUiReportRequest {
|
|
||||||
return {
|
|
||||||
type: "update_ui_report_req",
|
|
||||||
field_02: buf.readUInt16LE(0x0002),
|
|
||||||
field_04: buf.readUInt16LE(0x0004),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { UpdateUserLogRequest } from "../request/updateUserLog";
|
|
||||||
|
|
||||||
updateUserLog.msgCode = 0x00bd;
|
|
||||||
updateUserLog.msgLen = 0x0050;
|
|
||||||
|
|
||||||
export function updateUserLog(buf: Buffer): UpdateUserLogRequest {
|
|
||||||
return { type: "update_user_log_req" };
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import { Chara } from "../model/chara";
|
|
||||||
|
|
||||||
export function encodeChara(chara: Chara): Buffer {
|
|
||||||
const buf = Buffer.alloc(0x0014);
|
|
||||||
|
|
||||||
buf.writeUInt8(chara.gender === "male" ? 0 : 1, 0x00);
|
|
||||||
buf.writeUInt16LE(chara.field_02, 0x02);
|
|
||||||
buf.writeUInt16LE(chara.field_04, 0x04);
|
|
||||||
buf.writeUInt16LE(chara.field_06, 0x06);
|
|
||||||
buf.writeUInt16LE(chara.field_08, 0x08);
|
|
||||||
buf.writeUInt16LE(chara.field_0a, 0x0a);
|
|
||||||
buf.writeUInt16LE(chara.field_0c, 0x0c);
|
|
||||||
buf.writeUInt16LE(chara.field_0e, 0x0e);
|
|
||||||
buf.writeUInt16LE(chara.background, 0x10); // Swapped on save
|
|
||||||
buf.writeUInt16LE(chara.title, 0x12); // Swapped on save
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
import iconv from "iconv-lite";
|
|
||||||
|
|
||||||
import { CreateAutoTeamResponse } from "../response/createAutoTeam";
|
|
||||||
import { LoadTeamResponse } from "../response/loadTeam";
|
|
||||||
import { encodeChara } from "./_chara";
|
|
||||||
|
|
||||||
export function _team(res: CreateAutoTeamResponse | LoadTeamResponse) {
|
|
||||||
const buf = Buffer.alloc(0x0ca0);
|
|
||||||
|
|
||||||
if (res.type === "create_auto_team_res") {
|
|
||||||
buf.writeInt16LE(0x007c, 0x0000);
|
|
||||||
} else {
|
|
||||||
buf.writeInt16LE(0x0078, 0x0000);
|
|
||||||
}
|
|
||||||
|
|
||||||
const leader = res.members.find(item => item.leader);
|
|
||||||
|
|
||||||
buf.writeUInt32LE(res.team.extId, 0x000c);
|
|
||||||
iconv.encode(res.team.name, "shift_jis").copy(buf, 0x0024);
|
|
||||||
buf.writeUInt32LE(res.team.nameBg, 0x00d8);
|
|
||||||
buf.writeUInt32LE(res.team.nameFx, 0x00dc);
|
|
||||||
buf.fill(0xff, 0x00e0, 0x00f9); // Bitset: Unlocked BGs probably
|
|
||||||
buf.fill(0xff, 0x00f9, 0x0101); // Bitset: Unlocked FX probably
|
|
||||||
buf.writeUInt32LE(leader ? leader.profile.aimeId : 0, 0x0080);
|
|
||||||
|
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
const base = 0x011c + i * 0x005c;
|
|
||||||
const member = res.members[i];
|
|
||||||
|
|
||||||
if (member === undefined) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { profile, chara } = member;
|
|
||||||
const accessTime = (profile.accessTime.getTime() / 1000) | 0;
|
|
||||||
|
|
||||||
buf.writeInt32LE(profile.aimeId, base + 0x0000);
|
|
||||||
iconv.encode(profile.name + "\0", "shift_jis").copy(buf, base + 0x0004);
|
|
||||||
buf.writeInt32LE(profile.lv, base + 0x0018);
|
|
||||||
buf.writeInt32LE(0, base + 0x0024); // Month points, TODO
|
|
||||||
buf.writeUInt32LE(accessTime, base + 0x0034);
|
|
||||||
encodeChara(chara).copy(buf, base + 0x0044);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Team Time Attack:
|
|
||||||
|
|
||||||
/*for (let i = 0; i < 6; i++) {
|
|
||||||
const base = 0x0344 + 0x20 * i;
|
|
||||||
|
|
||||||
buf.writeInt16LE(0x00001, base + 0x0000);
|
|
||||||
buf.writeInt8(0x02, base + 0x0003);
|
|
||||||
buf.writeInt32LE(0x00000003, base + 0x0004);
|
|
||||||
iconv.encode("str\0", "shift_jis").copy(buf, base + 0x0008);
|
|
||||||
buf.writeInt32LE(0x00000004, base + 0x001c);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import { CheckTeamNameResponse } from "../response/checkTeamName";
|
|
||||||
|
|
||||||
export function checkTeamName(res: CheckTeamNameResponse): Buffer {
|
|
||||||
const buf = Buffer.alloc(0x0010);
|
|
||||||
|
|
||||||
buf.writeUInt16LE(0x00a3, 0x0000);
|
|
||||||
buf.writeUInt32LE(res.status, 0x0004);
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import { CreateTeamResponse } from "../response/createTeam";
|
|
||||||
|
|
||||||
export function createTeam(res: CreateTeamResponse): Buffer {
|
|
||||||
const buf = Buffer.alloc(0x0010);
|
|
||||||
|
|
||||||
buf.writeUInt16LE(0x0072, 0x0000);
|
|
||||||
buf.writeUInt32LE(res.status, 0x0004);
|
|
||||||
buf.writeUInt32LE(res.teamExtId, 0x0008);
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import { DiscoverProfileResponse } from "../response/discoverProfile";
|
|
||||||
|
|
||||||
export function discoverProfile(res: DiscoverProfileResponse) {
|
|
||||||
const buf = Buffer.alloc(0x0010);
|
|
||||||
|
|
||||||
buf.writeInt16LE(0x006c, 0x0000);
|
|
||||||
buf.writeInt8(res.exists ? 1 : 0, 0x0004);
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import { GenericResponse } from "../response/generic";
|
|
||||||
|
|
||||||
export function generic(res: GenericResponse) {
|
|
||||||
const buf = Buffer.alloc(0x0020);
|
|
||||||
|
|
||||||
buf.writeInt16LE(0x0001, 0x0000);
|
|
||||||
buf.writeInt32LE(res.status || 0, 0x0004);
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user