mirror of
https://github.com/djhackersdev/minime.git
synced 2026-03-22 10:14:32 -05:00
Compare commits
No commits in common. "master" and "v008" have entirely different histories.
|
|
@ -1,16 +0,0 @@
|
|||
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,8 +1,5 @@
|
|||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.rulers": [79],
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
|
|
|
|||
70
CHANGELOG.md
70
CHANGELOG.md
|
|
@ -1,70 +0,0 @@
|
|||
# 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
|
||||
5913
package-lock.json
generated
5913
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",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@decafcode/sqlite": "^2.0.0",
|
||||
"better-sqlite3": "^5.4.3",
|
||||
"compression": "^1.7.3",
|
||||
"date-fns": "^1.30.1",
|
||||
"debug": "^4.1.1",
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^5.4.0",
|
||||
"@types/compression": "^0.0.36",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/dotenv": "^6.1.1",
|
||||
|
|
@ -27,12 +28,12 @@
|
|||
"@types/jest": "^24.0.11",
|
||||
"@types/multiparty": "^0.0.32",
|
||||
"@types/node": "^12.11.7",
|
||||
"jest": "^26.4.2",
|
||||
"jest-haste-map": "^26.3.0",
|
||||
"jest": "^24.5.0",
|
||||
"jest-haste-map": "^24.5.0",
|
||||
"jest-resolve": "^24.5.0",
|
||||
"prettier": "^1.16.4",
|
||||
"ts-jest": "^26.3.0",
|
||||
"typescript": "^3.8.3",
|
||||
"ts-jest": "^24.0.0",
|
||||
"typescript": "^3.6.4",
|
||||
"utility-types": "^3.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -82,44 +82,6 @@ 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" (
|
||||
"id" integer primary key not null
|
||||
references "cm_user_data"("id")
|
||||
|
|
@ -160,23 +122,6 @@ create table "cm_user_data_ex" (
|
|||
"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" (
|
||||
"id" integer primary key not null,
|
||||
"profile_id" integer not null
|
||||
|
|
@ -357,16 +302,3 @@ create table "cm_user_playlog" (
|
|||
"place_name" 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,10 +7,6 @@ create table "idz_profile" (
|
|||
"player_id" integer not null
|
||||
references "aime_player"("id")
|
||||
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
|
||||
"name" text not null,
|
||||
"lv" integer not null,
|
||||
|
|
@ -20,7 +16,7 @@ create table "idz_profile" (
|
|||
"mileage" integer not null,
|
||||
"register_time" timestamp not null,
|
||||
"access_time" timestamp not null,
|
||||
constraint "idz_profile_player_uq" unique ("player_id", "version")
|
||||
constraint "idz_profile_player_uq" unique ("player_id")
|
||||
);
|
||||
|
||||
create table "idz_chara" (
|
||||
|
|
@ -104,27 +100,7 @@ create table "idz_settings" (
|
|||
"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")
|
||||
"gauges" integer not null
|
||||
);
|
||||
|
||||
create table "idz_story_state" (
|
||||
|
|
@ -144,7 +120,6 @@ create table "idz_story_cell_state" (
|
|||
"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",
|
||||
|
|
@ -227,13 +202,12 @@ create table "idz_unlocks" (
|
|||
|
||||
create table "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")
|
||||
constraint "idz_team_uq" unique ("ext_id")
|
||||
);
|
||||
|
||||
create table "idz_team_auto" (
|
||||
|
|
@ -241,7 +215,8 @@ create table "idz_team_auto" (
|
|||
references "idz_team"("id")
|
||||
on delete cascade,
|
||||
"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" (
|
||||
|
|
@ -265,25 +240,3 @@ create table "idz_team_reservation" (
|
|||
"join_time" timestamp 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")
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
-- 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")
|
||||
);
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
-- 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")
|
||||
);
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
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")
|
||||
);
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
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";
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
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";
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
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);
|
||||
|
||||
expect(obj.type).toBe("lookup2");
|
||||
expect(obj.type).toBe("lookup");
|
||||
expect(obj.gameId).toBe("SDBT");
|
||||
expect(obj.keychipId).toBe("A69E01A9999");
|
||||
expect(obj.luid).toBe("01036495850523030676");
|
||||
|
|
|
|||
|
|
@ -31,15 +31,6 @@ 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 {
|
||||
// idk what any of this stuff means yet
|
||||
// field20 and field28 appear to be an aime id but that is all.
|
||||
|
|
@ -107,7 +98,6 @@ readers.set(0x0009, readLogRequest);
|
|||
readers.set(0x000b, readCampaignRequest);
|
||||
readers.set(0x000d, readRegisterRequest);
|
||||
readers.set(0x000f, readLookupRequest2);
|
||||
readers.set(0x0011, readFeliCaLookupRequest2);
|
||||
readers.set(0x0064, readHelloRequest);
|
||||
readers.set(0x0066, readGoodbyeRequest);
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ registerLevels.set("segaid", 2);
|
|||
function begin(length: number) {
|
||||
const buf = Buffer.alloc(length);
|
||||
|
||||
buf.writeUInt16LE(0xa13e, 0x0000); // Magic: aime
|
||||
buf.writeUInt16LE(0xa13e, 0x0000); // Magic?
|
||||
buf.writeUInt16LE(0x3087, 0x0002); // ???
|
||||
buf.writeUInt16LE(length, 0x0006);
|
||||
|
||||
|
|
@ -43,18 +43,6 @@ export class Encoder extends Transform {
|
|||
|
||||
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":
|
||||
buf = begin(0x0020);
|
||||
buf.writeUInt16LE(0x0065, 0x0004); // cmd code
|
||||
|
|
|
|||
|
|
@ -46,28 +46,6 @@ function feliCaLookup(
|
|||
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(
|
||||
rep: Repositories,
|
||||
req: Req.LookupRequest,
|
||||
|
|
@ -137,9 +115,6 @@ export async function dispatch(
|
|||
case "felica_lookup":
|
||||
return feliCaLookup(rep, req, now);
|
||||
|
||||
case "felica_lookup2":
|
||||
return feliCaLookup2(rep, req, now);
|
||||
|
||||
case "lookup":
|
||||
return lookup(rep, req, now);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,6 @@ export interface FeliCaLookupRequest extends AimeRequestBase {
|
|||
pmm: string;
|
||||
}
|
||||
|
||||
export interface FeliCaLookup2Request extends AimeRequestBase {
|
||||
type: "felica_lookup2";
|
||||
idm: string;
|
||||
pmm: string;
|
||||
}
|
||||
|
||||
export interface RegisterRequest extends AimeRequestBase {
|
||||
type: "register";
|
||||
luid: string;
|
||||
|
|
@ -56,7 +50,6 @@ export interface GoodbyeRequest {
|
|||
|
||||
export type AimeRequest =
|
||||
| FeliCaLookupRequest
|
||||
| FeliCaLookup2Request
|
||||
| CampaignRequest
|
||||
| GoodbyeRequest
|
||||
| HelloRequest
|
||||
|
|
|
|||
|
|
@ -11,12 +11,6 @@ export interface FeliCaLookupResponse extends AimeResponseBase {
|
|||
accessCode: string;
|
||||
}
|
||||
|
||||
export interface FeliCaLookup2Response extends AimeResponseBase {
|
||||
type: "felica_lookup2";
|
||||
accessCode: string;
|
||||
aimeId?: AimeId;
|
||||
}
|
||||
|
||||
export interface CampaignResponse extends AimeResponseBase {
|
||||
type: "campaign";
|
||||
}
|
||||
|
|
@ -50,7 +44,6 @@ export interface RegisterResponse extends AimeResponseBase {
|
|||
|
||||
export type AimeResponse =
|
||||
| FeliCaLookupResponse
|
||||
| FeliCaLookup2Response
|
||||
| CampaignResponse
|
||||
| HelloResponse
|
||||
| LogResponse
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ class CardRepositoryImpl implements CardRepository {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const id = row.id!;
|
||||
const extId = row.ext_id!;
|
||||
const id = row.id;
|
||||
const extId = row.ext_id;
|
||||
|
||||
const touchSql = sql
|
||||
.update("aime_player")
|
||||
|
|
|
|||
|
|
@ -104,32 +104,30 @@ async function migratedb(
|
|||
}
|
||||
|
||||
export default async function checkdb(db: DataSource): Promise<void> {
|
||||
let schemaver: number | undefined;
|
||||
const stmt = sql.select("schemaver").from("meta");
|
||||
let maybe: number | undefined;
|
||||
|
||||
await db.maintenance(async txn => {
|
||||
const stmt = sql.select("schemaver").from("meta");
|
||||
try {
|
||||
const row = await db.transaction(txn => txn.fetchRow(stmt));
|
||||
|
||||
try {
|
||||
const row = await txn.fetchRow(stmt);
|
||||
|
||||
if (row !== undefined) {
|
||||
schemaver = parseInt(row.schemaver!);
|
||||
}
|
||||
} catch (e) {
|
||||
return await initdb(txn);
|
||||
if (row !== undefined) {
|
||||
maybe = parseInt(row.schemaver);
|
||||
}
|
||||
} catch (e) {
|
||||
return db.transaction(initdb);
|
||||
}
|
||||
|
||||
if (schemaver === undefined) {
|
||||
throw new Error(
|
||||
"Database corrupted: `meta` table singleton row is missing"
|
||||
);
|
||||
}
|
||||
if (maybe === undefined) {
|
||||
throw new Error(
|
||||
"Database corrupted: `meta` table singleton row is missing"
|
||||
);
|
||||
}
|
||||
|
||||
schemaver = await migratedb(txn, schemaver);
|
||||
});
|
||||
const schemaver = maybe;
|
||||
const newver = await db.transaction(txn => migratedb(txn, schemaver));
|
||||
|
||||
if (schemaver !== undefined) {
|
||||
debug("Upgraded database to version %s", schemaver);
|
||||
if (newver !== undefined) {
|
||||
debug("Upgraded database to version %s", newver);
|
||||
|
||||
await db.vacuum();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +1,13 @@
|
|||
import { GameChargeJson } from "../proto/gameCharge";
|
||||
import { Repositories } from "../repo";
|
||||
import { GetGameChargeRequest } from "../request/getGameCharge";
|
||||
import { GetGameChargeResponse } from "../response/getGameCharge";
|
||||
import { CHARGE_IDS } from "../static/charge";
|
||||
|
||||
export default async function getGameCharge(
|
||||
rep: Repositories,
|
||||
req: GetGameChargeRequest
|
||||
): 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 {
|
||||
length: gameChargeList.length.toString(),
|
||||
gameChargeList,
|
||||
length: "0",
|
||||
gameChargeList: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,14 @@
|
|||
import { GameEventJson } from "../proto/gameEvent";
|
||||
import { Repositories } from "../repo";
|
||||
import { GetGameEventRequest } from "../request/getGameEvent";
|
||||
import { GetGameEventResponse } from "../response/getGameEvent";
|
||||
import { EVENT_IDS } from "../static/event";
|
||||
|
||||
export default async function getGameEvent(
|
||||
rep: Repositories,
|
||||
req: GetGameEventRequest
|
||||
): 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 {
|
||||
type: req.type,
|
||||
length: gameEventList.length.toString(),
|
||||
gameEventList,
|
||||
length: "0",
|
||||
gameEventList: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { writeDate } from "../proto/base";
|
||||
import { Repositories } from "../repo";
|
||||
import { GetGameSettingRequest } from "../request/getGameSetting";
|
||||
import { GetGameSettingResponse } from "../response/getGameSetting";
|
||||
|
|
@ -7,25 +6,19 @@ export default async function getGameSetting(
|
|||
rep: Repositories,
|
||||
req: GetGameSettingRequest
|
||||
): Promise<GetGameSettingResponse> {
|
||||
const rebootStartTime = new Date();
|
||||
rebootStartTime.setHours(rebootStartTime.getHours() - 3);
|
||||
|
||||
const rebootEndTime = new Date();
|
||||
rebootEndTime.setHours(rebootEndTime.getHours() - 2);
|
||||
|
||||
return {
|
||||
gameSetting: {
|
||||
dataVersion: "1",
|
||||
isMaintenance: "false",
|
||||
requestInterval: "10",
|
||||
rebootStartTime: writeDate(rebootStartTime),
|
||||
rebootEndTime: writeDate(rebootEndTime),
|
||||
rebootStartTime: "0",
|
||||
rebootEndTime: "0",
|
||||
isBackgroundDistribute: "false",
|
||||
maxCountCharacter: "300",
|
||||
maxCountItem: "300",
|
||||
maxCountMusic: "100",
|
||||
maxCountCharacter: "999",
|
||||
maxCountItem: "999",
|
||||
maxCountMusic: "999",
|
||||
},
|
||||
isDumpUpload: "false",
|
||||
isAou: "true",
|
||||
isAou: "false",
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { readAimeId } from "../proto/base";
|
||||
import { writeUserCharge } from "../proto/userCharge";
|
||||
import { Repositories } from "../repo";
|
||||
import { GetUserChargeRequest } from "../request/getUserCharge";
|
||||
import { GetUserChargeResponse } from "../response/getUserCharge";
|
||||
|
|
@ -8,14 +6,9 @@ export default async function getUserCharge(
|
|||
rep: Repositories,
|
||||
req: GetUserChargeRequest
|
||||
): Promise<GetUserChargeResponse> {
|
||||
const aimeId = readAimeId(req.userId);
|
||||
|
||||
const profileId = await rep.userData().lookup(aimeId);
|
||||
const items = await rep.userCharge().load(profileId);
|
||||
|
||||
return {
|
||||
userId: req.userId,
|
||||
length: items.length.toString(),
|
||||
userChargeList: items.map(writeUserCharge),
|
||||
length: "0",
|
||||
userChargeList: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export default async function getUserCourse(
|
|||
.userCourse()
|
||||
.load(profileId, { limit: maxCount, offset: nextIndex });
|
||||
|
||||
|
||||
return {
|
||||
userId: req.userId,
|
||||
length: items.length.toString(),
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
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
|
||||
|
||||
const xout = itemKindN * itemKindMul + nextIndexN + BigInt(items.length);
|
||||
const xout = itemKindN * itemKindMul + BigInt(maxCount + items.length);
|
||||
|
||||
// Done:
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Repositories } from "../repo";
|
|||
import { GetUserRecentRatingRequest } from "../request/getUserRecentRating";
|
||||
import { GetUserRecentRatingResponse } from "../response/getUserRecentRating";
|
||||
import { readAimeId } from "../proto/base";
|
||||
import { writeUserRecentRating } from "../proto/userRecentRating";
|
||||
import { writeUserRecentRatingFromLog } from "../proto/userRecentRating";
|
||||
|
||||
export default async function getUserRecentRating(
|
||||
rep: Repositories,
|
||||
|
|
@ -12,11 +12,11 @@ export default async function getUserRecentRating(
|
|||
|
||||
const profileId = await rep.userData().lookup(aimeId);
|
||||
// Return recent 30 plays to calculate rating
|
||||
const items = await rep.userRecentRating().load(profileId);
|
||||
const items = await rep.userPlaylog().loadLatest(profileId, 30);
|
||||
|
||||
return {
|
||||
userId: req.userId,
|
||||
length: items.length.toString(),
|
||||
userRecentRatingList: items.map(writeUserRecentRating),
|
||||
userRecentRatingList: items.map(writeUserRecentRatingFromLog),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import getUserCourse from "./getUserCourse";
|
|||
import getUserData from "./getUserData";
|
||||
import getUserDataEx from "./getUserDataEx";
|
||||
import getUserDuel from "./getUserDuel";
|
||||
import getUserFavoriteMusic from "./getUserFavoriteMusic";
|
||||
import getUserItem from "./getUserItem";
|
||||
import getUserMap from "./getUserMap";
|
||||
import getUserMusic from "./getUserMusic";
|
||||
|
|
@ -33,7 +32,6 @@ import upsertClientError from "./upsertClientError";
|
|||
import upsertClientSetting from "./upsertClientSetting";
|
||||
import upsertClientTestmode from "./upsertClientTestmode";
|
||||
import upsertUserAll from "./upsertUserAll";
|
||||
import upsertUserChargelogApi from "./upsertUserChargelogApi";
|
||||
import createSqlWrapper from "../sql";
|
||||
import { DataSource } from "../../sql";
|
||||
|
||||
|
|
@ -100,14 +98,12 @@ export default function chunithm(db: DataSource) {
|
|||
wrapper.rpc("/GetUserDataApi", getUserData);
|
||||
wrapper.rpc("/GetUserDataExApi", getUserDataEx);
|
||||
wrapper.rpc("/GetUserDuelApi", getUserDuel);
|
||||
wrapper.rpc("/GetUserFavoriteMusicApi", getUserFavoriteMusic);
|
||||
wrapper.rpc("/GetUserItemApi", getUserItem);
|
||||
wrapper.rpc("/GetUserMapApi", getUserMap);
|
||||
wrapper.rpc("/GetUserMusicApi", getUserMusic);
|
||||
wrapper.rpc("/GetUserOptionApi", getUserOption);
|
||||
wrapper.rpc("/GetUserOptionExApi", getUserOptionEx);
|
||||
wrapper.rpc("/GetUserPreviewApi", getUserPreview);
|
||||
wrapper.rpc("/GetUserRecentPlayerApi", getUserRecentRating);
|
||||
wrapper.rpc("/GetUserRecentRatingApi", getUserRecentRating);
|
||||
wrapper.rpc("/GetUserRegionApi", getUserRegion);
|
||||
wrapper.rpc("/UpsertClientBookkeepingApi", upsertClientBookkeeping);
|
||||
|
|
@ -116,7 +112,6 @@ export default function chunithm(db: DataSource) {
|
|||
wrapper.rpc("/UpsertClientSettingApi", upsertClientSetting);
|
||||
wrapper.rpc("/UpsertClientTestmodeApi", upsertClientTestmode);
|
||||
wrapper.rpc("/UpsertUserAllApi", upsertUserAll);
|
||||
wrapper.rpc("/UpsertUserChargelogApi", upsertUserChargelogApi)
|
||||
|
||||
const app = express();
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ import { readUserActivity } from "../proto/userActivity";
|
|||
import { readUserDataEx } from "../proto/userDataEx";
|
||||
import { readUserDuelList } from "../proto/userDuelList";
|
||||
import { readUserPlaylog } from "../proto/userPlaylog";
|
||||
import { readUserCharge } from "../proto/userCharge";
|
||||
import { readUserRecentRating } from "../proto/userRecentRating";
|
||||
import { readUserCourse } from "../proto/userCourse";
|
||||
|
||||
// It shouldn't need to be said really, but seeing as this message (A) requires
|
||||
|
|
@ -69,10 +67,6 @@ export default async function upsertUserAll(
|
|||
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 || []) {
|
||||
await rep.userCourse().save(profileId, readUserCourse(item));
|
||||
}
|
||||
|
|
@ -85,12 +79,5 @@ export default async function upsertUserAll(
|
|||
await rep.userDuelList().save(profileId, readUserDuelList(item));
|
||||
}
|
||||
|
||||
await rep
|
||||
.userRecentRating()
|
||||
.save(
|
||||
profileId,
|
||||
(payload.userRecentRatingList || []).map(readUserRecentRating)
|
||||
);
|
||||
|
||||
return { returnCode: "1" };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
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",
|
||||
};
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
export interface UserChargeItem {
|
||||
chargeId: number;
|
||||
stock: number;
|
||||
purchaseDate: Date;
|
||||
validDate: Date;
|
||||
param1: number;
|
||||
param2: number;
|
||||
paramDate: Date;
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
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,3 +20,15 @@ export function writeUserRecentRating(
|
|||
): UserRecentRatingJson {
|
||||
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,8 +1,9 @@
|
|||
import { UserCourseRepository } from "./userCourse";
|
||||
|
||||
export { Page } from "./_defs";
|
||||
|
||||
import { UserActivityRepository } from "./userActivity";
|
||||
import { UserCharacterRepository } from "./userCharacter";
|
||||
import { UserCourseRepository } from "./userCourse";
|
||||
import { UserDataRepository } from "./userData";
|
||||
import { UserDataExRepository } from "./userDataEx";
|
||||
import { UserDuelListRepository } from "./userDuelList";
|
||||
|
|
@ -12,16 +13,12 @@ import { UserItemRepository } from "./userItem";
|
|||
import { UserMapRepository } from "./userMap";
|
||||
import { UserMusicRepository } from "./userMusic";
|
||||
import { UserPlaylogRepository } from "./userPlaylog";
|
||||
import { UserChargeRepository } from "./userCharge";
|
||||
import { UserRecentRatingRepository } from "./userRecentRating";
|
||||
|
||||
export interface Repositories {
|
||||
userActivity(): UserActivityRepository;
|
||||
|
||||
userCharacter(): UserCharacterRepository;
|
||||
|
||||
userCourse(): UserCourseRepository;
|
||||
|
||||
userData(): UserDataRepository;
|
||||
|
||||
userDataEx(): UserDataExRepository;
|
||||
|
|
@ -40,9 +37,5 @@ export interface Repositories {
|
|||
|
||||
userPlaylog(): UserPlaylogRepository;
|
||||
|
||||
userCharge(): UserChargeRepository;
|
||||
|
||||
userCourse(): UserCourseRepository;
|
||||
|
||||
userRecentRating(): UserRecentRatingRepository;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
import { UserChargeItem } from "../model/userCharge";
|
||||
import { RepositoryN } from "./_defs";
|
||||
|
||||
export type UserChargeRepository = RepositoryN<UserChargeItem>;
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
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>;
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
export interface GetUserFavoriteMusicRequest {
|
||||
/** Integer, AiMe ID */
|
||||
userId: string;
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import { UserMusicDetailJson } from "../proto/userMusic";
|
|||
import { UserActivityJson } from "../proto/userActivity";
|
||||
import { UserRecentRatingJson } from "../proto/userRecentRating";
|
||||
import { UserPlaylogJson } from "../proto/userPlaylog";
|
||||
import { UserChargeJson } from "../proto/userCharge";
|
||||
import { UserCourseJson } from "../proto/userCourse";
|
||||
import { UserDataExJson } from "../proto/userDataEx";
|
||||
import { UserDuelListJson } from "../proto/userDuelList";
|
||||
|
|
@ -28,7 +27,6 @@ export interface UpsertUserAllRequest {
|
|||
userActivityList?: UserActivityJson[];
|
||||
userRecentRatingList?: UserRecentRatingJson[];
|
||||
userPlaylogList?: UserPlaylogJson[];
|
||||
userChargeList?: UserChargeJson[];
|
||||
userCourseList?: UserCourseJson[];
|
||||
userDataEx?: UserDataExJson[];
|
||||
userDuelList?: UserDuelListJson[];
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
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,5 +1,3 @@
|
|||
import { UserChargeJson } from "../proto/userCharge";
|
||||
|
||||
export interface GetUserChargeResponse {
|
||||
/** Integer, AiMe ID */
|
||||
userId: string;
|
||||
|
|
@ -7,5 +5,6 @@ export interface GetUserChargeResponse {
|
|||
/** Integer, number of results returned */
|
||||
length: string;
|
||||
|
||||
userChargeList: UserChargeJson[];
|
||||
/** TBD */
|
||||
userChargeList: [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
export interface GetUserFavoriteMusicResponse {
|
||||
/** Integer, AiMe ID */
|
||||
userId: string;
|
||||
|
||||
/** Integer, number of results returned */
|
||||
length: string;
|
||||
|
||||
/** TBD */
|
||||
userFavoriteMusicList: [];
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export interface UpsertUserChargelogApiResponse {
|
||||
returnCode: string;
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
import { SqlUserActivityRepository } from "./userActivity";
|
||||
import { SqlUserCharacterRepository } from "./userCharacter";
|
||||
import { SqlUserChargeRepository } from "./userCharge";
|
||||
import { SqlUserCourseRepository } from "./userCourse";
|
||||
import { SqlUserDataRepository } from "./userData";
|
||||
import { SqlUserDataExRepository } from "./userDataEx";
|
||||
import { SqlUserDuelListRepository } from "./userDuelList";
|
||||
|
|
@ -11,12 +9,9 @@ import { SqlUserItemRepository } from "./userItem";
|
|||
import { SqlUserMapRepository } from "./userMap";
|
||||
import { SqlUserMusicRepository } from "./userMusic";
|
||||
import { SqlUserPlaylogRepository } from "./userPlaylog";
|
||||
import { SqlUserRecentRatingRepository } from "./userRecentRating";
|
||||
import { Repositories } from "../repo";
|
||||
import { UserActivityRepository } from "../repo/userActivity";
|
||||
import { UserCharacterRepository } from "../repo/userCharacter";
|
||||
import { UserChargeRepository } from "../repo/userCharge";
|
||||
import { UserCourseRepository } from "../repo/userCourse";
|
||||
import { UserDataRepository } from "../repo/userData";
|
||||
import { UserDataExRepository } from "../repo/userDataEx";
|
||||
import { UserDuelListRepository } from "../repo/userDuelList";
|
||||
|
|
@ -26,8 +21,9 @@ import { UserItemRepository } from "../repo/userItem";
|
|||
import { UserMapRepository } from "../repo/userMap";
|
||||
import { UserMusicRepository } from "../repo/userMusic";
|
||||
import { UserPlaylogRepository } from "../repo/userPlaylog";
|
||||
import { UserRecentRatingRepository } from "../repo/userRecentRating";
|
||||
import { Transaction } from "../../sql";
|
||||
import { UserCourseRepository } from "../repo/userCourse";
|
||||
import { SqlUserCourseRepository } from "./userCourse";
|
||||
|
||||
export class SqlRepositories implements Repositories {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
|
@ -40,14 +36,6 @@ export class SqlRepositories implements Repositories {
|
|||
return new SqlUserCharacterRepository(this._txn);
|
||||
}
|
||||
|
||||
userCharge(): UserChargeRepository {
|
||||
return new SqlUserChargeRepository(this._txn);
|
||||
}
|
||||
|
||||
userCourse(): UserCourseRepository {
|
||||
return new SqlUserCourseRepository(this._txn);
|
||||
}
|
||||
|
||||
userData(): UserDataRepository {
|
||||
return new SqlUserDataRepository(this._txn);
|
||||
}
|
||||
|
|
@ -84,7 +72,7 @@ export class SqlRepositories implements Repositories {
|
|||
return new SqlUserPlaylogRepository(this._txn);
|
||||
}
|
||||
|
||||
userRecentRating(): UserRecentRatingRepository {
|
||||
return new SqlUserRecentRatingRepository(this._txn);
|
||||
userCourse(): UserCourseRepository {
|
||||
return new SqlUserCourseRepository(this._txn);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,9 +28,7 @@ export class SqlUserActivityRepository implements UserActivityRepository {
|
|||
.select("*")
|
||||
.from("cm_user_activity")
|
||||
.where("profile_id", profileId)
|
||||
.where("kind", kind)
|
||||
.orderBy("sort_number DESC")
|
||||
.limit(100);
|
||||
.where("kind", kind);
|
||||
|
||||
const rows = await this._txn.fetchRows(stmt);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
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,10 +37,7 @@ export class SqlUserCourseRepository implements UserCourseRepository {
|
|||
.from("cm_user_course")
|
||||
.where("profile_id", profileId);
|
||||
|
||||
/**
|
||||
* UserCourse has no paging before CHUNITHM Amazon
|
||||
*/
|
||||
if (page && !isNaN(page.limit)) {
|
||||
if (page) {
|
||||
stmt.limit(page.limit).offset(page.offset);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,47 +56,7 @@ export class SqlUserDataExRepository implements UserDataExRepository {
|
|||
const row = await this._txn.fetchRow(stmt);
|
||||
|
||||
if (row === undefined) {
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
throw new Error("UserDataEx record not found");
|
||||
}
|
||||
|
||||
return readRow(row);
|
||||
|
|
|
|||
|
|
@ -34,22 +34,14 @@ export class SqlUserMusicRepository implements UserMusicRepository {
|
|||
profileId: Id<UserDataItem>,
|
||||
page?: Page
|
||||
): 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
|
||||
.select("*")
|
||||
.from("cm_user_music")
|
||||
.where("profile_id", profileId)
|
||||
.and(sql.in('music_id', musicIds));
|
||||
.where("profile_id", profileId);
|
||||
|
||||
if (page) {
|
||||
stmt.limit(page.limit).offset(page.offset);
|
||||
}
|
||||
|
||||
const rows = await this._txn.fetchRows(stmt);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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,
|
||||
},
|
||||
]);
|
||||
|
|
@ -1,854 +0,0 @@
|
|||
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,
|
||||
]);
|
||||
|
|
@ -1,17 +1,4 @@
|
|||
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) {
|
||||
export function byteString(n: bigint, length: number) {
|
||||
const result = Buffer.alloc(length);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
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,5 +0,0 @@
|
|||
import setup from "./setup";
|
||||
|
||||
export { BLOCK_SIZE } from "./aes";
|
||||
export { ClientHello } from "./setup";
|
||||
export default setup;
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
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 };
|
||||
}
|
||||
8
src/idz/decoder/checkTeamName.ts
Normal file
8
src/idz/decoder/checkTeamName.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { CheckTeamNameRequest } from "../request/checkTeamName";
|
||||
|
||||
checkTeamName.msgCode = 0x00a2;
|
||||
checkTeamName.msgLen = 0x0040;
|
||||
|
||||
export function checkTeamName(buf: Buffer): CheckTeamNameRequest {
|
||||
return { type: "check_team_name_req" };
|
||||
}
|
||||
14
src/idz/decoder/createAutoTeam.ts
Normal file
14
src/idz/decoder/createAutoTeam.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
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),
|
||||
};
|
||||
}
|
||||
24
src/idz/decoder/createProfile.ts
Normal file
24
src/idz/decoder/createProfile.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
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)),
|
||||
};
|
||||
}
|
||||
24
src/idz/decoder/createTeam.ts
Normal file
24
src/idz/decoder/createTeam.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
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"),
|
||||
};
|
||||
}
|
||||
12
src/idz/decoder/discoverProfile.ts
Normal file
12
src/idz/decoder/discoverProfile.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
206
src/idz/decoder/index.ts
Normal file
206
src/idz/decoder/index.ts
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
30
src/idz/decoder/load2on2.ts
Normal file
30
src/idz/decoder/load2on2.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
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>,
|
||||
};
|
||||
}
|
||||
8
src/idz/decoder/loadConfig.ts
Normal file
8
src/idz/decoder/loadConfig.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { LoadConfigRequest } from "../request/loadConfig";
|
||||
|
||||
loadConfig.msgCode = 0x0004;
|
||||
loadConfig.msgLen = 0x0050;
|
||||
|
||||
export function loadConfig(): LoadConfigRequest {
|
||||
return { type: "load_config_req" };
|
||||
}
|
||||
8
src/idz/decoder/loadConfig2.ts
Normal file
8
src/idz/decoder/loadConfig2.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { LoadConfigRequest2 } from "../request/loadConfig2";
|
||||
|
||||
loadConfig2.msgCode = 0x00ab;
|
||||
loadConfig2.msgLen = 0x0010;
|
||||
|
||||
export function loadConfig2(): LoadConfigRequest2 {
|
||||
return { type: "load_config_v2_req" };
|
||||
}
|
||||
12
src/idz/decoder/loadEventInfo.ts
Normal file
12
src/idz/decoder/loadEventInfo.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
12
src/idz/decoder/loadGacha.ts
Normal file
12
src/idz/decoder/loadGacha.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
14
src/idz/decoder/loadGarage.ts
Normal file
14
src/idz/decoder/loadGarage.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
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,32 +1,27 @@
|
|||
import { LoadGeneralRewardRequest } from "../request/loadGeneralReward";
|
||||
import { AimeId } from "../../../model";
|
||||
import {
|
||||
LoadGeneralRewardRequest1,
|
||||
LoadGeneralRewardRequest2,
|
||||
} from "../request/loadGeneralReward";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadGeneralReward1.msgCode = 0x009c;
|
||||
loadGeneralReward1.msgLen = 0x0010;
|
||||
|
||||
export function loadGeneralReward1(buf: Buffer): LoadGeneralRewardRequest {
|
||||
export function loadGeneralReward1(buf: Buffer): LoadGeneralRewardRequest1 {
|
||||
return {
|
||||
type: "load_general_reward_req",
|
||||
format: 1,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
|
||||
loadGeneralReward2.msgCode = 0x093b;
|
||||
loadGeneralReward2.msgCode = 0x013b;
|
||||
loadGeneralReward2.msgLen = 0x0010;
|
||||
|
||||
export function loadGeneralReward2(buf: Buffer): LoadGeneralRewardRequest {
|
||||
return {
|
||||
type: "load_general_reward_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
|
||||
loadGeneralReward3.msgCode = 0x013b;
|
||||
loadGeneralReward3.msgLen = 0x0010;
|
||||
|
||||
export function loadGeneralReward3(buf: Buffer): LoadGeneralRewardRequest {
|
||||
export function loadGeneralReward2(buf: Buffer): LoadGeneralRewardRequest2 {
|
||||
return {
|
||||
type: "load_general_reward_req",
|
||||
format: 2,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
15
src/idz/decoder/loadGhost.ts
Normal file
15
src/idz/decoder/loadGhost.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
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),
|
||||
};
|
||||
}
|
||||
29
src/idz/decoder/loadProfile.ts
Normal file
29
src/idz/decoder/loadProfile.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
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"),
|
||||
};
|
||||
}
|
||||
10
src/idz/decoder/loadRewardTable.ts
Normal file
10
src/idz/decoder/loadRewardTable.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { LoadRewardTableRequest } from "../request/loadRewardTable";
|
||||
|
||||
loadRewardTable.msgCode = 0x0086;
|
||||
loadRewardTable.msgLen = 0x0010;
|
||||
|
||||
export function loadRewardTable(): LoadRewardTableRequest {
|
||||
return {
|
||||
type: "load_reward_table_req",
|
||||
};
|
||||
}
|
||||
12
src/idz/decoder/loadStocker.ts
Normal file
12
src/idz/decoder/loadStocker.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
16
src/idz/decoder/loadTeam.ts
Normal file
16
src/idz/decoder/loadTeam.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
21
src/idz/decoder/loadTeamRanking.ts
Normal file
21
src/idz/decoder/loadTeamRanking.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
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",
|
||||
};
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import {
|
|||
LoadTopTenRequest,
|
||||
LoadTopTenRequestSelector,
|
||||
} from "../request/loadTopTen";
|
||||
import { AimeId } from "../../../model";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadTopTen1.msgCode = 0x00b5;
|
||||
loadTopTen1.msgLen = 0x00e0;
|
||||
|
|
@ -24,7 +24,6 @@ export function loadTopTen1(buf: Buffer): LoadTopTenRequest {
|
|||
|
||||
return {
|
||||
type: "load_top_ten_req",
|
||||
version: 1,
|
||||
field_2: buf.readUInt16LE(0x0002), // Bitmask selector
|
||||
selectors,
|
||||
field_C4: buf.readUInt8(0x00c4), // Boolean, true if profile ID is set
|
||||
|
|
@ -4,7 +4,7 @@ import {
|
|||
LoadTopTenRequest,
|
||||
LoadTopTenRequestSelector,
|
||||
} from "../request/loadTopTen";
|
||||
import { AimeId } from "../../../model";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadTopTen2.msgCode = 0x012c;
|
||||
loadTopTen2.msgLen = 0x0110;
|
||||
|
|
@ -24,7 +24,6 @@ export function loadTopTen2(buf: Buffer): LoadTopTenRequest {
|
|||
|
||||
return {
|
||||
type: "load_top_ten_req",
|
||||
version: 1,
|
||||
field_2: buf.readUInt16LE(0x0002), // Bitmask selector
|
||||
selectors,
|
||||
field_C4: buf.readUInt8(0x00f4), // Boolean, true if profile ID is set
|
||||
|
|
@ -34,34 +33,3 @@ export function loadTopTen2(buf: Buffer): LoadTopTenRequest {
|
|||
teamId: teamId !== 0xffffffff ? (teamId as ExtId<Team>) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
loadTopTen3.msgCode = 0x012c;
|
||||
loadTopTen3.msgLen = 0x0110;
|
||||
|
||||
export function loadTopTen3(buf: Buffer): LoadTopTenRequest {
|
||||
const selectors = new Array<LoadTopTenRequestSelector>();
|
||||
|
||||
for (let i = 0; i < 40; i++) {
|
||||
selectors.push({
|
||||
routeNo: (buf.readUInt16LE(0x0004 + 2 + 2 * i) >> 1) as RouteNo,
|
||||
minTimestamp: new Date(
|
||||
buf.readUInt32LE(0x0054 + 4 + 4 * i) * 1000 + 1000
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
const profileId = buf.readUInt32LE(0x00fc);
|
||||
const teamId = buf.readUInt32LE(0x0100);
|
||||
|
||||
return {
|
||||
type: "load_top_ten_req",
|
||||
version: 2,
|
||||
field_2: buf.readUInt16LE(0x0004), // Bitmask selector
|
||||
selectors,
|
||||
field_C4: buf.readUInt8(0x00f8), // Boolean, true if profile ID is set
|
||||
field_C5: buf.readUInt8(0x00f9), // Always zero
|
||||
field_C6: buf.readUInt16LE(0x00fa),
|
||||
aimeId: profileId !== 0 ? (profileId as AimeId) : undefined,
|
||||
teamId: teamId !== 0xffffffff ? (teamId as ExtId<Team>) : undefined,
|
||||
};
|
||||
}
|
||||
11
src/idz/decoder/lockGarage.ts
Normal file
11
src/idz/decoder/lockGarage.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
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),
|
||||
};
|
||||
}
|
||||
13
src/idz/decoder/lockProfile.ts
Normal file
13
src/idz/decoder/lockProfile.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
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),
|
||||
};
|
||||
}
|
||||
13
src/idz/decoder/lockProfileExtend.ts
Normal file
13
src/idz/decoder/lockProfileExtend.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
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"),
|
||||
};
|
||||
}
|
||||
8
src/idz/decoder/msg00AD.ts
Normal file
8
src/idz/decoder/msg00AD.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { Message00AD } from "../request/msg00AD";
|
||||
|
||||
msg00AD.msgCode = 0x00ad;
|
||||
msg00AD.msgLen = 0x061c;
|
||||
|
||||
export function msg00AD(buf: Buffer): Message00AD {
|
||||
return { type: "msg_00AD_req" };
|
||||
}
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
import { SaveExpeditionRequest } from "../request/saveExpedition";
|
||||
import {
|
||||
SaveExpeditionRequest1,
|
||||
SaveExpeditionRequest2,
|
||||
} from "../request/saveExpedition";
|
||||
|
||||
saveExpedition1.msgCode = 0x008c;
|
||||
saveExpedition1.msgLen = 0x0010;
|
||||
|
||||
export function saveExpedition1(buf: Buffer): SaveExpeditionRequest {
|
||||
export function saveExpedition1(buf: Buffer): SaveExpeditionRequest1 {
|
||||
return {
|
||||
type: "save_expedition_req",
|
||||
format: 1,
|
||||
field_0004: buf.readUInt32LE(0x0004),
|
||||
};
|
||||
}
|
||||
|
|
@ -13,9 +17,10 @@ export function saveExpedition1(buf: Buffer): SaveExpeditionRequest {
|
|||
saveExpedition2.msgCode = 0x013f;
|
||||
saveExpedition2.msgLen = 0x0010;
|
||||
|
||||
export function saveExpedition2(buf: Buffer): SaveExpeditionRequest {
|
||||
export function saveExpedition2(buf: Buffer): SaveExpeditionRequest2 {
|
||||
return {
|
||||
type: "save_expedition_req",
|
||||
format: 2,
|
||||
field_0004: buf.readUInt32LE(0x0004),
|
||||
};
|
||||
}
|
||||
22
src/idz/decoder/saveGarage.ts
Normal file
22
src/idz/decoder/saveGarage.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
16
src/idz/decoder/saveNewCar.ts
Normal file
16
src/idz/decoder/saveNewCar.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
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,33 +1,31 @@
|
|||
import { car } from "./_car";
|
||||
import { mission } from "./_mission";
|
||||
import { BackgroundCode, CourseNo, TitleCode } from "../model/base";
|
||||
import { StoryCell, StoryRow } from "../model/story";
|
||||
import { SaveProfileRequest } from "../request/saveProfile";
|
||||
import { SaveProfileRequest2 } from "../request/saveProfile";
|
||||
import { bitmap } from "./_bitmap";
|
||||
import { AimeId } from "../../../model";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
saveProfile2.msgCode = 0x0068;
|
||||
saveProfile2.msgLen = 0x0940;
|
||||
|
||||
export function saveProfile2(buf: Buffer): SaveProfileRequest {
|
||||
const storyRows = new Map<number, StoryRow>();
|
||||
export function saveProfile2(buf: Buffer): SaveProfileRequest2 {
|
||||
const storyRows = new Array();
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const cells = new Map<number, StoryCell>();
|
||||
const cells = new Array();
|
||||
const rowOffset = 0x01a8 + i * 0x3c;
|
||||
|
||||
for (let j = 0; j < 9; j++) {
|
||||
const a = buf.readUInt32LE(rowOffset + 0x04 + j * 4);
|
||||
const b = buf.readUInt16LE(rowOffset + 0x28 + j * 2);
|
||||
const c = 0; // Added in idz2
|
||||
const cell = { a, b, c };
|
||||
const cell = { a, b };
|
||||
|
||||
cells.set(j, cell);
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
const row = { cells };
|
||||
|
||||
storyRows.set(i, row);
|
||||
storyRows.push(row);
|
||||
}
|
||||
|
||||
const coursePlays = new Map<CourseNo, number>();
|
||||
|
|
@ -47,8 +45,8 @@ export function saveProfile2(buf: Buffer): SaveProfileRequest {
|
|||
|
||||
return {
|
||||
type: "save_profile_req",
|
||||
format: 2,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
version: 1,
|
||||
lv: buf.readUInt16LE(0x0026),
|
||||
exp: buf.readUInt32LE(0x0028),
|
||||
fame: buf.readUInt32LE(0x0468),
|
||||
|
|
@ -96,7 +94,6 @@ export function saveProfile2(buf: Buffer): SaveProfileRequest {
|
|||
aura: buf.readUInt8(0x002c),
|
||||
paperCup: buf.readUInt8(0x00f6),
|
||||
gauges: buf.readUInt8(0x00f7),
|
||||
drivingStyle: 0, // Not supported until idz2
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,35 +1,33 @@
|
|||
import { car } from "./_car";
|
||||
import { mission } from "./_mission";
|
||||
import { BackgroundCode, CourseNo, TitleCode } from "../model/base";
|
||||
import { StoryCell, StoryRow } from "../model/story";
|
||||
import { SaveProfileRequest } from "../request/saveProfile";
|
||||
import { SaveProfileRequest2 } from "../request/saveProfile";
|
||||
import { bitmap } from "./_bitmap";
|
||||
import { AimeId } from "../../../model";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
saveProfile3.msgCode = 0x0138;
|
||||
saveProfile3.msgLen = 0x0a70;
|
||||
|
||||
export function saveProfile3(buf: Buffer): SaveProfileRequest {
|
||||
const storyRows = new Map<number, StoryRow>();
|
||||
export function saveProfile3(buf: Buffer): SaveProfileRequest2 {
|
||||
const storyRows = new Array();
|
||||
|
||||
// Story layout has changed somewhat...
|
||||
|
||||
for (let i = 0; i < 27; i++) {
|
||||
const cells = new Map<number, StoryCell>();
|
||||
const cells = new Array();
|
||||
const rowOffset = 0x01ac + i * 0x18;
|
||||
|
||||
for (let j = 0; j < 9; j++) {
|
||||
const a = buf.readUInt8(rowOffset + 0x00 + j);
|
||||
const b = buf.readUInt8(rowOffset + 0x09 + j);
|
||||
const c = 0; // Added in idz2
|
||||
const cell = { a, b, c };
|
||||
const cell = { a, b };
|
||||
|
||||
cells.set(j, cell);
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
const row = { cells };
|
||||
|
||||
storyRows.set(i, row);
|
||||
storyRows.push(row);
|
||||
}
|
||||
|
||||
const coursePlays = new Map<CourseNo, number>();
|
||||
|
|
@ -49,8 +47,8 @@ export function saveProfile3(buf: Buffer): SaveProfileRequest {
|
|||
|
||||
return {
|
||||
type: "save_profile_req",
|
||||
format: 2,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
version: 1,
|
||||
lv: buf.readUInt16LE(0x0026),
|
||||
exp: buf.readUInt32LE(0x0028),
|
||||
fame: buf.readUInt32LE(0x04fc),
|
||||
|
|
@ -98,7 +96,6 @@ export function saveProfile3(buf: Buffer): SaveProfileRequest {
|
|||
aura: buf.readUInt8(0x002c),
|
||||
paperCup: buf.readUInt8(0x00f6),
|
||||
gauges: buf.readUInt8(0x00f7),
|
||||
drivingStyle: 0, // Not supported until idz2
|
||||
},
|
||||
};
|
||||
}
|
||||
23
src/idz/decoder/saveSettings.ts
Normal file
23
src/idz/decoder/saveSettings.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
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),
|
||||
};
|
||||
}
|
||||
19
src/idz/decoder/saveStocker.ts
Normal file
19
src/idz/decoder/saveStocker.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
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)),
|
||||
};
|
||||
}
|
||||
15
src/idz/decoder/saveTeamBanner.ts
Normal file
15
src/idz/decoder/saveTeamBanner.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
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 +1,12 @@
|
|||
import { RouteNo } from "../model/base";
|
||||
import { CarSelector } from "../model/car";
|
||||
import { SaveTimeAttackRequest } from "../request/saveTimeAttack";
|
||||
import { AimeId } from "../../../model";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
function saveTimeAttack(buf: Buffer): SaveTimeAttackRequest {
|
||||
return {
|
||||
type: "save_time_attack_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
version: 1,
|
||||
dayNight: buf.readUInt8(0x0054) & 1,
|
||||
payload: {
|
||||
routeNo: (buf.readUInt8(0x0054) >> 1) as RouteNo,
|
||||
|
|
@ -49,35 +48,3 @@ saveTimeAttack2.msgLen = 0x0080;
|
|||
export function saveTimeAttack2(buf: Buffer): SaveTimeAttackRequest {
|
||||
return saveTimeAttack(buf);
|
||||
}
|
||||
|
||||
saveTimeAttack3.msgCode = 0x0136;
|
||||
saveTimeAttack3.msgLen = 0x0080;
|
||||
|
||||
export function saveTimeAttack3(buf: Buffer): SaveTimeAttackRequest {
|
||||
return {
|
||||
type: "save_time_attack_req",
|
||||
aimeId: buf.readUInt32LE(0x0008) as AimeId,
|
||||
version: 2,
|
||||
dayNight: buf.readUInt8(0x0058) & 1,
|
||||
payload: {
|
||||
routeNo: (buf.readUInt8(0x0058) >> 1) as RouteNo,
|
||||
timestamp: new Date(buf.readUInt32LE(0x005c) * 1000),
|
||||
flags: buf.readUInt8(0x0060),
|
||||
totalTime: buf.readUInt32LE(0x001c) / 1000,
|
||||
sectionTimes: [
|
||||
buf.readUInt32LE(0x0028) / 1000,
|
||||
buf.readUInt32LE(0x002c) / 1000,
|
||||
buf.readUInt32LE(0x0030) / 1000,
|
||||
],
|
||||
grade: buf.readUInt8(0x0066),
|
||||
carSelector: buf.readUInt16LE(0x0010) as CarSelector,
|
||||
},
|
||||
field_0002: buf.readUInt16LE(0x0004),
|
||||
field_0008: buf.readUInt32LE(0x000c),
|
||||
field_0012: buf.readUInt8(0x0016),
|
||||
field_0015: buf.readUInt8(0x0019),
|
||||
field_005D: buf.readUInt8(0x0061),
|
||||
field_005E: buf.readUInt16LE(0x0062),
|
||||
field_0060: buf.readUInt16LE(0x0064),
|
||||
};
|
||||
}
|
||||
13
src/idz/decoder/saveTopic.ts
Normal file
13
src/idz/decoder/saveTopic.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
12
src/idz/decoder/unlockProfile.ts
Normal file
12
src/idz/decoder/unlockProfile.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
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"),
|
||||
};
|
||||
}
|
||||
13
src/idz/decoder/updateProvisionalStoreRank.ts
Normal file
13
src/idz/decoder/updateProvisionalStoreRank.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
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),
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user