chunithm: Initial implementation code drop

No cleaner development history than this exists. It's difficult to
methodically design something when you don't yet know what it is
you're designing.
This commit is contained in:
Tau
2019-11-06 17:29:07 -05:00
parent beb9144e9b
commit d1d63e1ef9
169 changed files with 3903 additions and 242 deletions
+304
View File
@@ -0,0 +1,304 @@
-- SQLite's ALTER TABLE statement is almost useless, and who knows how much of
-- this stuff is present in older versions. So while ideally we'd mark a lot
-- of these columns NOT NULL that might come back to bite us as we extend
-- support downward from Chunithm 4 to Chunithm 1.
create table "cm_user_data" (
"id" integer primary key not null,
-- Could in theory do a shared primary key with aime_player, since this
-- is going to be a 1:1 relation for the foreseeable future, but again
-- the SQLite strait-jacket forces us to design defensively.
"player_id" integer not null
references "aime_player"("id")
on delete cascade,
"access_code" text,
"user_name" text,
"is_web_join" text,
"web_limit_date" text,
"level" integer,
"reincarnation_num" integer,
"exp" integer,
"point" integer,
"total_point" integer,
"play_count" integer,
"multi_play_count" integer,
"multi_win_count" integer,
"request_res_count" integer,
"accept_res_count" integer,
"success_res_count" integer,
"player_rating" integer,
"highest_rating" integer,
"nameplate_id" integer,
"frame_id" integer,
"character_id" integer,
"trophy_id" integer,
"played_tutorial_bit" integer,
"first_tutorial_cancel_num" integer,
"master_tutorial_cancel_num" integer,
"total_repertoire_count" integer,
"total_map_num" integer,
"total_hi_score" integer,
"total_basic_high_score" integer,
"total_advanced_high_score" integer,
"total_expert_high_score" integer,
"total_master_high_score" integer,
"event_watched_date" text,
"friend_count" integer,
"is_maimai" text,
"first_game_id" text,
"first_rom_version" text,
"first_data_version" text,
"first_play_date" text,
"last_game_id" text,
"last_rom_version" text,
"last_data_version" text,
"last_play_date" text,
"last_place_id" integer,
"last_place_name" text,
"last_region_id" text,
"last_region_name" text,
"last_all_net_id" text,
"last_client_id" text
);
create table "cm_user_activity" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"kind" integer not null,
-- This is just called "id" on the wire but, well, we already have an "id"
-- column up there ^
"activity_id" integer not null,
"sort_number" integer not null,
"param1" integer not null,
"param2" integer not null,
"param3" integer not null,
"param4" integer not null,
constraint "cm_user_activity_uq" unique (
"profile_id",
"kind",
"activity_id"
)
);
create table "cm_user_data_ex" (
"id" integer primary key not null
references "cm_user_data"("id")
on delete cascade,
"compatible_cm_version" text not null,
"medal" integer not null,
"map_icon_id" integer not null,
"voice_id" integer not null,
"ext1" integer not null,
"ext2" integer not null,
"ext3" integer not null,
"ext4" integer not null,
"ext5" integer not null,
"ext6" integer not null,
"ext7" integer not null,
"ext8" integer not null,
"ext9" integer not null,
"ext10" integer not null,
"ext11" integer not null,
"ext12" integer not null,
"ext13" integer not null,
"ext14" integer not null,
"ext15" integer not null,
"ext16" integer not null,
"ext17" integer not null,
"ext18" integer not null,
"ext19" integer not null,
"ext20" integer not null,
"ext_str1" text not null,
"ext_str2" text not null,
"ext_str3" text not null,
"ext_str4" text not null,
"ext_str5" text not null,
"ext_long1" integer not null,
"ext_long2" integer not null,
"ext_long3" integer not null,
"ext_long4" integer not null,
"ext_long5" integer not null
);
create table "cm_user_character" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"character_id" integer not null,
"play_count" integer not null,
"level" integer not null,
"skill_id" integer not null,
"friendship_exp" integer not null,
"is_valid" integer not null,
"is_new_mark" integer not null,
"param1" integer not null,
"param2" integer not null,
constraint "cm_user_character_uq" unique ("profile_id", "character_id")
);
create table "cm_user_game_option" (
"id" integer primary key not null
references "cm_user_data"("id")
on delete cascade,
"bg_info" integer not null,
"field_color" integer not null,
"guide_sound" integer not null,
"sound_effect" integer not null,
"guide_line" integer not null,
"speed" integer not null,
"option_set" integer not null,
"matching" integer not null,
"judge_pos" integer not null,
"rating" integer not null,
"judge_justice" integer not null,
"judge_attack" integer not null,
"headphone" integer not null,
"player_level" integer not null,
"success_tap" integer not null,
"success_ex_tap" integer not null,
"success_slide_hold" integer not null,
"success_air" integer not null,
"success_flick" integer not null,
"success_skill" integer not null,
"success_tap_timbre" integer not null,
"privacy" integer not null
);
create table "cm_user_game_option_ex" (
"id" integer primary key not null
references "cm_user_data"("id")
on delete cascade,
"ext1" integer not null,
"ext2" integer not null,
"ext3" integer not null,
"ext4" integer not null,
"ext5" integer not null,
"ext6" integer not null,
"ext7" integer not null,
"ext8" integer not null,
"ext9" integer not null,
"ext10" integer not null,
"ext11" integer not null,
"ext12" integer not null,
"ext13" integer not null,
"ext14" integer not null,
"ext15" integer not null,
"ext16" integer not null,
"ext17" integer not null,
"ext18" integer not null,
"ext19" integer not null,
"ext20" integer not null
);
create table "cm_user_item" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"item_kind" integer not null,
"item_id" integer not null,
"stock" integer not null,
"is_valid" text not null,
constraint "cm_user_item_uq" unique ("profile_id", "item_kind", "item_id")
);
create table "cm_user_map" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"map_id" integer not null,
"position" integer not null,
"is_clear" text not null,
"area_id" integer not null,
"route_number" integer not null,
"event_id" integer not null,
"rate" integer not null,
"status_count" integer not null,
"is_valid" text not null,
constraint "cm_user_map_uq" unique ("profile_id", "map_id")
);
create table "cm_user_music" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"music_id" integer not null,
"level" integer not null,
"play_count" integer not null,
"score_max" integer not null,
"res_request_count" integer not null,
"res_accept_count" integer not null,
"res_success_count" integer not null,
"miss_count" integer not null,
"max_combo_count" integer not null,
"is_full_combo" text not null,
"is_all_justice" text not null,
"is_success" text not null,
"full_chain" integer not null,
"max_chain" integer not null,
"score_rank" integer not null,
"is_lock" text not null,
constraint "cm_user_music_uq" unique ("profile_id", "music_id", "level")
);
create table "cm_user_playlog" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"order_id" integer not null,
"sort_number" integer not null,
"place_id" integer not null,
"play_date" text not null,
"user_play_date" text not null,
"music_id" integer not null,
"level" integer not null,
"custom_id" integer not null,
"played_user_id1" integer not null,
"played_user_id2" integer not null,
"played_user_id3" integer not null,
"played_user_name1" text not null,
"played_user_name2" text not null,
"played_user_name3" text not null,
"played_music_level1" integer not null,
"played_music_level2" integer not null,
"played_music_level3" integer not null,
"played_custom1" integer not null,
"played_custom2" integer not null,
"played_custom3" integer not null,
"track" integer not null,
"score" integer not null,
"rank" integer not null,
"max_combo" integer not null,
"max_chain" integer not null,
"rate_tap" integer not null,
"rate_hold" integer not null,
"rate_slide" integer not null,
"rate_air" integer not null,
"rate_flick" integer not null,
"judge_guilty" integer not null,
"judge_attack" integer not null,
"judge_justice" integer not null,
"judge_critical" integer not null,
"event_id" integer not null,
"player_rating" integer not null,
"is_new_record" text not null,
"is_full_combo" text not null,
"full_chain_kind" integer not null,
"is_all_justice" text not null,
"is_continue" text not null,
"is_free_to_play" text not null,
"character_id" integer not null,
"skill_id" integer not null,
"play_kind" integer not null,
"is_clear" text not null,
"skill_level" integer not null,
"skill_effect" integer not null,
"place_name" text not null,
"is_maimai" text not null
);
+304
View File
@@ -0,0 +1,304 @@
-- SQLite's ALTER TABLE statement is almost useless, and who knows how much of
-- this stuff is present in older versions. So while ideally we'd mark a lot
-- of these columns NOT NULL that might come back to bite us as we extend
-- support downward from Chunithm 4 to Chunithm 1.
create table "cm_user_data" (
"id" integer primary key not null,
-- Could in theory do a shared primary key with aime_player, since this
-- is going to be a 1:1 relation for the foreseeable future, but again
-- the SQLite strait-jacket forces us to design defensively.
"player_id" integer not null
references "aime_player"("id")
on delete cascade,
"access_code" text,
"user_name" text,
"is_web_join" text,
"web_limit_date" text,
"level" integer,
"reincarnation_num" integer,
"exp" integer,
"point" integer,
"total_point" integer,
"play_count" integer,
"multi_play_count" integer,
"multi_win_count" integer,
"request_res_count" integer,
"accept_res_count" integer,
"success_res_count" integer,
"player_rating" integer,
"highest_rating" integer,
"nameplate_id" integer,
"frame_id" integer,
"character_id" integer,
"trophy_id" integer,
"played_tutorial_bit" integer,
"first_tutorial_cancel_num" integer,
"master_tutorial_cancel_num" integer,
"total_repertoire_count" integer,
"total_map_num" integer,
"total_hi_score" integer,
"total_basic_high_score" integer,
"total_advanced_high_score" integer,
"total_expert_high_score" integer,
"total_master_high_score" integer,
"event_watched_date" text,
"friend_count" integer,
"is_maimai" text,
"first_game_id" text,
"first_rom_version" text,
"first_data_version" text,
"first_play_date" text,
"last_game_id" text,
"last_rom_version" text,
"last_data_version" text,
"last_play_date" text,
"last_place_id" integer,
"last_place_name" text,
"last_region_id" text,
"last_region_name" text,
"last_all_net_id" text,
"last_client_id" text
);
create table "cm_user_activity" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"kind" integer not null,
-- This is just called "id" on the wire but, well, we already have an "id"
-- column up there ^
"activity_id" integer not null,
"sort_number" integer not null,
"param1" integer not null,
"param2" integer not null,
"param3" integer not null,
"param4" integer not null,
constraint "cm_user_activity_uq" unique (
"profile_id",
"kind",
"activity_id"
)
);
create table "cm_user_data_ex" (
"id" integer primary key not null
references "cm_user_data"("id")
on delete cascade,
"compatible_cm_version" text not null,
"medal" integer not null,
"map_icon_id" integer not null,
"voice_id" integer not null,
"ext1" integer not null,
"ext2" integer not null,
"ext3" integer not null,
"ext4" integer not null,
"ext5" integer not null,
"ext6" integer not null,
"ext7" integer not null,
"ext8" integer not null,
"ext9" integer not null,
"ext10" integer not null,
"ext11" integer not null,
"ext12" integer not null,
"ext13" integer not null,
"ext14" integer not null,
"ext15" integer not null,
"ext16" integer not null,
"ext17" integer not null,
"ext18" integer not null,
"ext19" integer not null,
"ext20" integer not null,
"ext_str1" text not null,
"ext_str2" text not null,
"ext_str3" text not null,
"ext_str4" text not null,
"ext_str5" text not null,
"ext_long1" integer not null,
"ext_long2" integer not null,
"ext_long3" integer not null,
"ext_long4" integer not null,
"ext_long5" integer not null
);
create table "cm_user_character" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"character_id" integer not null,
"play_count" integer not null,
"level" integer not null,
"skill_id" integer not null,
"friendship_exp" integer not null,
"is_valid" integer not null,
"is_new_mark" integer not null,
"param1" integer not null,
"param2" integer not null,
constraint "cm_user_character_uq" unique ("profile_id", "character_id")
);
create table "cm_user_game_option" (
"id" integer primary key not null
references "cm_user_data"("id")
on delete cascade,
"bg_info" integer not null,
"field_color" integer not null,
"guide_sound" integer not null,
"sound_effect" integer not null,
"guide_line" integer not null,
"speed" integer not null,
"option_set" integer not null,
"matching" integer not null,
"judge_pos" integer not null,
"rating" integer not null,
"judge_justice" integer not null,
"judge_attack" integer not null,
"headphone" integer not null,
"player_level" integer not null,
"success_tap" integer not null,
"success_ex_tap" integer not null,
"success_slide_hold" integer not null,
"success_air" integer not null,
"success_flick" integer not null,
"success_skill" integer not null,
"success_tap_timbre" integer not null,
"privacy" integer not null
);
create table "cm_user_game_option_ex" (
"id" integer primary key not null
references "cm_user_data"("id")
on delete cascade,
"ext1" integer not null,
"ext2" integer not null,
"ext3" integer not null,
"ext4" integer not null,
"ext5" integer not null,
"ext6" integer not null,
"ext7" integer not null,
"ext8" integer not null,
"ext9" integer not null,
"ext10" integer not null,
"ext11" integer not null,
"ext12" integer not null,
"ext13" integer not null,
"ext14" integer not null,
"ext15" integer not null,
"ext16" integer not null,
"ext17" integer not null,
"ext18" integer not null,
"ext19" integer not null,
"ext20" integer not null
);
create table "cm_user_item" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"item_kind" integer not null,
"item_id" integer not null,
"stock" integer not null,
"is_valid" text not null,
constraint "cm_user_item_uq" unique ("profile_id", "item_kind", "item_id")
);
create table "cm_user_map" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"map_id" integer not null,
"position" integer not null,
"is_clear" text not null,
"area_id" integer not null,
"route_number" integer not null,
"event_id" integer not null,
"rate" integer not null,
"status_count" integer not null,
"is_valid" text not null,
constraint "cm_user_map_uq" unique ("profile_id", "map_id")
);
create table "cm_user_music" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"music_id" integer not null,
"level" integer not null,
"play_count" integer not null,
"score_max" integer not null,
"res_request_count" integer not null,
"res_accept_count" integer not null,
"res_success_count" integer not null,
"miss_count" integer not null,
"max_combo_count" integer not null,
"is_full_combo" text not null,
"is_all_justice" text not null,
"is_success" text not null,
"full_chain" integer not null,
"max_chain" integer not null,
"score_rank" integer not null,
"is_lock" text not null,
constraint "cm_user_music_uq" unique ("profile_id", "music_id", "level")
);
create table "cm_user_playlog" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"order_id" integer not null,
"sort_number" integer not null,
"place_id" integer not null,
"play_date" text not null,
"user_play_date" text not null,
"music_id" integer not null,
"level" integer not null,
"custom_id" integer not null,
"played_user_id1" integer not null,
"played_user_id2" integer not null,
"played_user_id3" integer not null,
"played_user_name1" text not null,
"played_user_name2" text not null,
"played_user_name3" text not null,
"played_music_level1" integer not null,
"played_music_level2" integer not null,
"played_music_level3" integer not null,
"played_custom1" integer not null,
"played_custom2" integer not null,
"played_custom3" integer not null,
"track" integer not null,
"score" integer not null,
"rank" integer not null,
"max_combo" integer not null,
"max_chain" integer not null,
"rate_tap" integer not null,
"rate_hold" integer not null,
"rate_slide" integer not null,
"rate_air" integer not null,
"rate_flick" integer not null,
"judge_guilty" integer not null,
"judge_attack" integer not null,
"judge_justice" integer not null,
"judge_critical" integer not null,
"event_id" integer not null,
"player_rating" integer not null,
"is_new_record" text not null,
"is_full_combo" text not null,
"full_chain_kind" integer not null,
"is_all_justice" text not null,
"is_continue" text not null,
"is_free_to_play" text not null,
"character_id" integer not null,
"skill_id" integer not null,
"play_kind" integer not null,
"is_clear" text not null,
"skill_level" integer not null,
"skill_effect" integer not null,
"place_name" text not null,
"is_maimai" text not null
);
+1
View File
@@ -9,6 +9,7 @@ const scripts = [
// Order matters! Later scripts reference schema instantiated earlier.
"meta.sql",
"aime.sql",
"cm.sql",
"idz.sql",
];
-21
View File
@@ -1,21 +0,0 @@
import { Request, Response } from "express";
export default function getGameCharge(req: Request, res: Response) {
res.json({
length: 0,
gameChargeList: [
/*
{
orderId: 1,
chargeId: 1,
price: 1,
startDate: 'STRINGIDK',
endDate: 'STRINGIDK',
salePrice:
saleStartDate: 'STRINGIDK',
saleEndDate: 'STRINGIDK',
},
*/
],
});
}
-18
View File
@@ -1,18 +0,0 @@
import { Request, Response } from "express";
export default function getGameEvent(req: Request, res: Response) {
res.json({
type: 1,
length: 0,
gameEventList: [
/*
{
type: 1,
id: 1102, // data/A000/event/event00001102
startDate: 'STRINGIDK',
endDate: 'STRINGIDK',
},
*/
],
});
}
-11
View File
@@ -1,11 +0,0 @@
import { Request, Response } from "express";
export default function getGameIdlist(req: Request, res: Response) {
const { type } = req.body;
res.json({
type,
length: 0,
gameIdlistList: [],
});
}
-17
View File
@@ -1,17 +0,0 @@
import { Request, Response } from "express";
export default function getGameMessage(req: Request, res: Response) {
res.json({
type: 1,
length: 0,
gameMessageList: [
/* {
type: 2,
id: 1,
message: "true",
startDate: "0",
endDate: "0"
},*/
],
});
}
-18
View File
@@ -1,18 +0,0 @@
import { Request, Response } from "express";
export default function getGameRanking(req: Request, res: Response) {
const { type } = req.body;
res.json({
type,
gameRankingList: [
/*
// QWORD fields maybe?
{
id: 1,
point: 1,
}
*/
],
});
}
-22
View File
@@ -1,22 +0,0 @@
import { Request, Response } from "express";
export default function getGameSale(req: Request, res: Response) {
const { type } = req.body;
res.json({
type,
length: 0,
gameSaleList: [
/*
{
orderId: 1234,
type,
id: 4321,
rate: 5678,
startDate: 'STRINGIDK',
endDate: 'STRINGIDK',
},
*/
],
});
}
-19
View File
@@ -1,19 +0,0 @@
import { Request, Response } from "express";
export default function getGameSetting(req: Request, res: Response) {
res.json({
gameSetting: {
dataVersion: 1,
isMaintenance: false,
requestInterval: 10,
rebootStartTime: 0,
rebootEndTime: 0,
isBackgroundDistribute: false,
maxCountCharacter: 999,
maxCountItem: 999,
maxCountMusic: 999,
},
isDumpUpload: false,
isAou: false,
});
}
+13
View File
@@ -0,0 +1,13 @@
// Make the names explicit so that we don't get the order mixed up
interface Params {
maxCount: number;
nextIndex: number;
}
export function paginationCookie<T>(items: T[], params: Params) {
if (items.length < params.maxCount) {
return "-1";
} else {
return (params.nextIndex + params.maxCount).toString();
}
}
+10
View File
@@ -0,0 +1,10 @@
import { Repositories } from "../repo";
import { GameLoginRequest } from "../request/gameLogin";
import { GameLoginResponse } from "../response/gameLogin";
export default async function gameLogin(
rep: Repositories,
req: GameLoginRequest
): Promise<GameLoginResponse> {
return { returnCode: "1" };
}
+10
View File
@@ -0,0 +1,10 @@
import { Repositories } from "../repo";
import { GameLogoutRequest } from "../request/gameLogout";
import { GameLogoutResponse } from "../response/gameLogout";
export default async function gameLogout(
rep: Repositories,
req: GameLogoutRequest
): Promise<GameLogoutResponse> {
return { returnCode: "1" };
}
+13
View File
@@ -0,0 +1,13 @@
import { Repositories } from "../repo";
import { GetGameChargeRequest } from "../request/getGameCharge";
import { GetGameChargeResponse } from "../response/getGameCharge";
export default async function getGameCharge(
rep: Repositories,
req: GetGameChargeRequest
): Promise<GetGameChargeResponse> {
return {
length: "0",
gameChargeList: [],
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetGameEventRequest } from "../request/getGameEvent";
import { GetGameEventResponse } from "../response/getGameEvent";
export default async function getGameEvent(
rep: Repositories,
req: GetGameEventRequest
): Promise<GetGameEventResponse> {
return {
type: req.type,
length: "0",
gameEventList: [],
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetGameIdlistRequest } from "../request/getGameIdlist";
import { GetGameIdlistResponse } from "../response/getGameIdlist";
export default async function getGameIdlist(
rep: Repositories,
req: GetGameIdlistRequest
): Promise<GetGameIdlistResponse> {
return {
type: req.type,
length: "0",
gameIdlistList: [],
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetGameMessageRequest } from "../request/getGameMessage";
import { GetGameMessageResponse } from "../response/getGameMessage";
export default async function getGameMessage(
rep: Repositories,
req: GetGameMessageRequest
): Promise<GetGameMessageResponse> {
return {
type: req.type,
length: "0",
gameMessageList: [],
};
}
+13
View File
@@ -0,0 +1,13 @@
import { Repositories } from "../repo";
import { GetGameRankingRequest } from "../request/getGameRanking";
import { GetGameRankingResponse } from "../response/getGameRanking";
export default async function getGameRanking(
rep: Repositories,
req: GetGameRankingRequest
): Promise<GetGameRankingResponse> {
return {
type: req.type,
gameRankingList: [],
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetGameSaleRequest } from "../request/getGameSale";
import { GetGameSaleResponse } from "../response/getGameSale";
export default async function getGameSale(
rep: Repositories,
req: GetGameSaleRequest
): Promise<GetGameSaleResponse> {
return {
type: req.type,
length: "0",
gameSaleList: [],
};
}
+24
View File
@@ -0,0 +1,24 @@
import { Repositories } from "../repo";
import { GetGameSettingRequest } from "../request/getGameSetting";
import { GetGameSettingResponse } from "../response/getGameSetting";
export default async function getGameSetting(
rep: Repositories,
req: GetGameSettingRequest
): Promise<GetGameSettingResponse> {
return {
gameSetting: {
dataVersion: "1",
isMaintenance: "false",
requestInterval: "10",
rebootStartTime: "0",
rebootEndTime: "0",
isBackgroundDistribute: "false",
maxCountCharacter: "999",
maxCountItem: "999",
maxCountMusic: "999",
},
isDumpUpload: "false",
isAou: "false",
};
}
+23
View File
@@ -0,0 +1,23 @@
import { readAimeId } from "../proto/base";
import { writeUserActivity } from "../proto/userActivity";
import { Repositories } from "../repo";
import { GetUserActivityRequest } from "../request/getUserActivity";
import { GetUserActivityResponse } from "../response/getUserActivity";
export default async function getUserActivity(
rep: Repositories,
req: GetUserActivityRequest
): Promise<GetUserActivityResponse> {
const aimeId = readAimeId(req.userId);
const kind = parseInt(req.kind);
const profileId = await rep.userData().lookup(aimeId);
const items = await rep.userActivity().load(profileId, kind);
return {
userId: req.userId,
length: items.length.toString(),
kind: kind.toString(),
userActivityList: items.map(writeUserActivity),
};
}
+27
View File
@@ -0,0 +1,27 @@
import { paginationCookie } from "./_util";
import { readAimeId } from "../proto/base";
import { writeUserCharacter } from "../proto/userCharacter";
import { Repositories } from "../repo";
import { GetUserCharacterRequest } from "../request/getUserCharacter";
import { GetUserCharacterResponse } from "../response/getUserCharacter";
export default async function getUserCharacter(
rep: Repositories,
req: GetUserCharacterRequest
): Promise<GetUserCharacterResponse> {
const aimeId = readAimeId(req.userId);
const maxCount = parseInt(req.maxCount);
const nextIndex = parseInt(req.nextIndex);
const profileId = await rep.userData().lookup(aimeId);
const items = await rep
.userCharacter()
.load(profileId, { limit: maxCount, offset: nextIndex });
return {
userId: req.userId,
length: items.length.toString(),
nextIndex: paginationCookie(items, { maxCount, nextIndex }),
userCharacterList: items.map(writeUserCharacter),
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetUserChargeRequest } from "../request/getUserCharge";
import { GetUserChargeResponse } from "../response/getUserCharge";
export default async function getUserCharge(
rep: Repositories,
req: GetUserChargeRequest
): Promise<GetUserChargeResponse> {
return {
userId: req.userId,
length: "0",
userChargeList: [],
};
}
+17
View File
@@ -0,0 +1,17 @@
import { Repositories } from "../repo";
import { GetUserCourseRequest } from "../request/getUserCourse";
import { GetUserCourseResponse } from "../response/getUserCourse";
export default async function getUserCourse(
rep: Repositories,
req: GetUserCourseRequest
): Promise<GetUserCourseResponse> {
return {
userId: req.userId,
length: "0",
nextIndex: "-1",
userCourseList: [
// This will get saved at some point I think
],
};
}
+20
View File
@@ -0,0 +1,20 @@
import { readAimeId } from "../proto/base";
import { writeUserData } from "../proto/userData";
import { Repositories } from "../repo";
import { GetUserDataRequest } from "../request/getUserData";
import { GetUserDataResponse } from "../response/getUserData";
export default async function getUserData(
rep: Repositories,
req: GetUserDataRequest
): Promise<GetUserDataResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const userData = await rep.userData().load(profileId);
return {
userId: req.userId,
userData: writeUserData(userData),
};
}
+20
View File
@@ -0,0 +1,20 @@
import { readAimeId } from "../proto/base";
import { writeUserDataEx } from "../proto/userDataEx";
import { Repositories } from "../repo";
import { GetUserDataExRequest } from "../request/getUserDataEx";
import { GetUserDataExResponse } from "../response/getUserDataEx";
export default async function getUserDataEx(
rep: Repositories,
req: GetUserDataExRequest
): Promise<GetUserDataExResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const userDataEx = await rep.userDataEx().load(profileId);
return {
userId: req.userId,
userDataEx: writeUserDataEx(userDataEx),
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetUserDuelRequest } from "../request/getUserDuel";
import { GetUserDuelResponse } from "../response/getUserDuel";
export default async function getUserDuel(
rep: Repositories,
req: GetUserDuelRequest
): Promise<GetUserDuelResponse> {
return {
userId: req.userId,
length: "0",
userDuelList: [],
};
}
+62
View File
@@ -0,0 +1,62 @@
import { readAimeId } from "../proto/base";
import { writeUserItem } from "../proto/userItem";
import { Repositories } from "../repo";
import { GetUserItemRequest } from "../request/getUserItem";
import { GetUserItemResponse } from "../response/getUserItem";
import { UserItemItem } from "../model/userItem";
// I'm not sure why they went to all this trouble just to avoid passing an
// explicit `itemKind` parameter in the request...
const itemKindMul = 10000000000n;
export default async function getUserItem(
rep: Repositories,
req: GetUserItemRequest
): Promise<GetUserItemResponse> {
// Split the "nextIndex" parameter
const compoundIn = BigInt(req.nextIndex);
const itemKindN = compoundIn / itemKindMul;
const nextIndexN = compoundIn % itemKindMul;
// Now unpack the entire request
const aimeId = readAimeId(req.userId);
const maxCount = parseInt(req.maxCount);
const itemKind = parseInt(itemKindN as any);
const nextIndex = parseInt(nextIndexN as any);
// Hit DB
// This gets called for not-yet-created profiles for some reason (probably
// so that the server can force some bonus items into a new profile's
// inventory)? So we need to gracefully handle that condition.
// (maybe some sort of anchor row is created on first GameLogin...)
const profileId = await rep.userData().tryLookup(aimeId);
let items: UserItemItem[];
if (profileId !== undefined) {
items = await rep
.userItem()
.load(profileId, itemKind, { limit: maxCount, offset: nextIndex });
} else {
items = [];
}
// Pack the next pagination cookie into bigint compound form
const compoundOut =
itemKindN * itemKindMul + BigInt(maxCount + items.length);
// Done:
return {
userId: req.userId,
length: items.length.toString(),
nextIndex: items.length < maxCount ? "-1" : compoundOut.toString(),
itemKind: itemKind.toString(),
userItemList: items.map(writeUserItem),
};
}
+21
View File
@@ -0,0 +1,21 @@
import { readAimeId } from "../proto/base";
import { writeUserMap } from "../proto/userMap";
import { Repositories } from "../repo";
import { GetUserMapRequest } from "../request/getUserMap";
import { GetUserMapResponse } from "../response/getUserMap";
export default async function getUserMap(
rep: Repositories,
req: GetUserMapRequest
): Promise<GetUserMapResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const items = await rep.userMap().load(profileId);
return {
userId: req.userId,
length: items.length.toString(),
userMapList: items.map(writeUserMap),
};
}
+50
View File
@@ -0,0 +1,50 @@
import { writeUserMusicDetail, UserMusicJson } from "../proto/userMusic";
import { Repositories } from "../repo";
import { GetUserMusicRequest } from "../request/getUserMusic";
import { GetUserMusicResponse } from "../response/getUserMusic";
import { readAimeId } from "../proto/base";
import { UserMusicDetailItem } from "../model/userMusic";
import { paginationCookie } from "./_util";
export default async function getUserMusic(
rep: Repositories,
req: GetUserMusicRequest
): Promise<GetUserMusicResponse> {
const aimeId = readAimeId(req.userId);
const maxCount = parseInt(req.maxCount);
const nextIndex = parseInt(req.nextIndex);
const profileId = await rep.userData().lookup(aimeId);
const flat = await rep
.userMusic()
.load(profileId, { limit: maxCount, offset: nextIndex });
const groupIdx = new Map<number, UserMusicDetailItem[]>();
for (const item of flat) {
const { musicId } = item;
const existing = groupIdx.get(musicId);
if (existing !== undefined) {
existing.push(item);
} else {
groupIdx.set(musicId, [item]);
}
}
const userMusicList = new Array<UserMusicJson>();
for (const group of groupIdx.values()) {
userMusicList.push({
length: group.length.toString(),
userMusicDetailList: group.map(writeUserMusicDetail),
});
}
return {
userId: req.userId,
length: userMusicList.length.toString(),
nextIndex: paginationCookie(flat, { maxCount, nextIndex }),
userMusicList,
};
}
+20
View File
@@ -0,0 +1,20 @@
import { readAimeId } from "../proto/base";
import { writeUserGameOption } from "../proto/userGameOption";
import { Repositories } from "../repo";
import { GetUserOptionRequest } from "../request/getUserOption";
import { GetUserOptionResponse } from "../response/getUserOption";
export default async function getUserOption(
rep: Repositories,
req: GetUserOptionRequest
): Promise<GetUserOptionResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const userGameOption = await rep.userGameOption().load(profileId);
return {
userId: req.userId,
userGameOption: writeUserGameOption(userGameOption),
};
}
+20
View File
@@ -0,0 +1,20 @@
import { readAimeId } from "../proto/base";
import { writeUserGameOptionEx } from "../proto/userGameOptionEx";
import { Repositories } from "../repo";
import { GetUserOptionExRequest } from "../request/getUserOptionEx";
import { GetUserOptionExResponse } from "../response/getUserOptionEx";
export default async function getUserOptionEx(
rep: Repositories,
req: GetUserOptionExRequest
): Promise<GetUserOptionExResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const userGameOptionEx = await rep.userGameOptionEx().load(profileId);
return {
userId: req.userId,
userGameOptionEx: writeUserGameOptionEx(userGameOptionEx),
};
}
+58
View File
@@ -0,0 +1,58 @@
import { readAimeId, writeDate } from "../proto/base";
import { writeUserCharacter } from "../proto/userCharacter";
import { Repositories } from "../repo";
import { GetUserPreviewRequest } from "../request/getUserPreview";
import { GetUserPreviewResponse } from "../response/getUserPreview";
export default async function getUserPreview(
rep: Repositories,
req: GetUserPreviewRequest
): Promise<GetUserPreviewResponse | null> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().tryLookup(aimeId);
// Return an empty JSON object if the player lacks a profile; this will
// start the new profile registration flow in the client.
if (profileId === undefined) {
return null;
}
const userData = await rep.userData().load(profileId);
const userCharacter = await rep
.userCharacter()
.loadOne(profileId, userData.characterId);
const userGameOption = await rep.userGameOption().load(profileId);
return {
userId: "1",
// Current login (i.e. profile lock) status
isLogin: "false",
lastLoginDate: "1970-01-01 09:00:00",
// UserData:
userName: userData.userName,
reincarnationNum: userData.reincarnationNum.toString(),
level: userData.level.toString(),
exp: userData.exp.toString(),
playerRating: userData.playerRating.toString(),
lastGameId: userData.lastGameId,
lastRomVersion: userData.lastRomVersion,
lastDataVersion: userData.lastDataVersion,
lastPlayDate: writeDate(userData.lastPlayDate),
trophyId: userData.trophyId.toString(),
// Currently selected UserCharacter:
userCharacter: writeUserCharacter(userCharacter),
// UserGameOption:
playerLevel: userGameOption.playerLevel.toString(),
rating: userGameOption.rating.toString(),
headphone: userGameOption.headphone.toString(),
};
}
@@ -0,0 +1,16 @@
import { Repositories } from "../repo";
import { GetUserRecentRatingRequest } from "../request/getUserRecentRating";
import { GetUserRecentRatingResponse } from "../response/getUserRecentRating";
export default async function getUserRecentRating(
rep: Repositories,
req: GetUserRecentRatingRequest
): Promise<GetUserRecentRatingResponse> {
// I don't really know what subset of data to return here.
return {
userId: req.userId,
length: "0",
userRecentRatingList: [],
};
}
+16
View File
@@ -0,0 +1,16 @@
import { Repositories } from "../repo";
import { GetUserRegionRequest } from "../request/getUserRegion";
import { GetUserRegionResponse } from "../response/getUserRegion";
export default async function getUserRegion(
rep: Repositories,
req: GetUserRegionRequest
): Promise<GetUserRegionResponse> {
return {
userId: req.userId,
length: "0",
userRegionList: [
// Doesn't get saved
],
};
}
+126
View File
@@ -0,0 +1,126 @@
import compression from "compression";
import express, { NextFunction, Request, Response } from "express";
import logger from "debug";
import gameLogin from "./gameLogin";
import gameLogout from "./gameLogout";
import getGameCharge from "./getGameCharge";
import getGameEvent from "./getGameEvent";
import getGameIdlist from "./getGameIdlist";
import getGameMessage from "./getGameMessage";
import getGameRanking from "./getGameRanking";
import getGameSale from "./getGameSale";
import getGameSetting from "./getGameSetting";
import getUserActivity from "./getUserActivity";
import getUserCharacter from "./getUserCharacter";
import getUserCharge from "./getUserCharge";
import getUserCourse from "./getUserCourse";
import getUserData from "./getUserData";
import getUserDataEx from "./getUserDataEx";
import getUserDuel from "./getUserDuel";
import getUserItem from "./getUserItem";
import getUserMap from "./getUserMap";
import getUserMusic from "./getUserMusic";
import getUserOption from "./getUserOption";
import getUserOptionEx from "./getUserOptionEx";
import getUserPreview from "./getUserPreview";
import getUserRecentRating from "./getUserRecentRating";
import getUserRegion from "./getUserRegion";
import upsertClientBookkeeping from "./upsertClientBookkeeping";
import upsertClientDevelop from "./upsertClientDevelop";
import upsertClientError from "./upsertClientError";
import upsertClientSetting from "./upsertClientSetting";
import upsertClientTestmode from "./upsertClientTestmode";
import upsertUserAll from "./upsertUserAll";
import createSqlWrapper from "../sql";
import { DataSource } from "../../sql";
const debug = logger("app:chuni:io");
// Thankfully we can use standard middleware for JSON I/O. We have to use a
// zlib middleware as well because compression is non-optional: the client will
// attempt to inflate whatever response we send to it whether there's a
// Transfer-Encoding header or not.
function quirks(req: Request, res: Response, next: NextFunction) {
// Chunithm client does not follow the HTTP spec: user agents must indicate
// that they are willing to accept a particular Transfer-Encoding for that
// T-E to be used (unless the T-E in question is "chunked" but w/e), and this
// indication is advisory anyway, the server is permitted to ignore it.
//
// Force this header into the request, since it is not present normally.
// ... unless, that is, the client is actually somebody debugging with curl
// and not the real game client, in which case this forced deflate can be
// annoying. Pass an "X-Debug" request header to suppress forced compression.
if (req.headers["x-debug"] === undefined) {
req.headers["accept-encoding"] = "deflate";
}
return next();
}
function trace(req: Request, res: Response, next: NextFunction) {
debug("\n--- Chunithm %s ---\n", req.url);
debug("Request: %j", req.body);
const prevJson = res.json;
res.json = function(obj) {
debug("Response: %j", obj);
res.json = prevJson;
res.json.apply(this, arguments);
return res;
};
return next();
}
export default function chunithm(db: DataSource) {
const wrapper = createSqlWrapper(db);
wrapper.rpc("/GameLoginApi", gameLogin);
wrapper.rpc("/GameLogoutApi", gameLogout);
wrapper.rpc("/GetGameChargeApi", getGameCharge);
wrapper.rpc("/GetGameEventApi", getGameEvent);
wrapper.rpc("/GetGameIdlistApi", getGameIdlist);
wrapper.rpc("/GetGameMessageApi", getGameMessage);
wrapper.rpc("/GetGameRankingApi", getGameRanking);
wrapper.rpc("/GetGameSaleApi", getGameSale);
wrapper.rpc("/GetGameSettingApi", getGameSetting);
wrapper.rpc("/GetUserActivityApi", getUserActivity);
wrapper.rpc("/GetUserCharacterApi", getUserCharacter);
wrapper.rpc("/GetUserChargeApi", getUserCharge);
wrapper.rpc("/GetUserCourseAPi", getUserCourse);
wrapper.rpc("/GetUserDataApi", getUserData);
wrapper.rpc("/GetUserDataExApi", getUserDataEx);
wrapper.rpc("/GetUserDuelApi", getUserDuel);
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("/GetUserRecentRatingApi", getUserRecentRating);
wrapper.rpc("/GetUserRegionApi", getUserRegion);
wrapper.rpc("/UpsertClientBookkeepingApi", upsertClientBookkeeping);
wrapper.rpc("/UpsertClientDevelopApi", upsertClientDevelop);
wrapper.rpc("/UpsertClientErrorApi", upsertClientError);
wrapper.rpc("/UpsertClientSettingApi", upsertClientSetting);
wrapper.rpc("/UpsertClientTestmodeApi", upsertClientTestmode);
wrapper.rpc("/UpsertUserAllApi", upsertUserAll);
const app = express();
app.use(quirks);
app.use(compression({ threshold: 0 }));
app.use(express.json());
app.use(trace);
app.use("/ChuniServlet", wrapper);
return app;
}
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientBookkeepingRequest } from "../request/upsertClientBookkeeping";
import { UpsertClientBookkeepingResponse } from "../response/upsertClientBookkeeping";
export default async function upsertClientBookkeeping(
rep: Repositories,
req: UpsertClientBookkeepingRequest
): Promise<UpsertClientBookkeepingResponse> {
return {
returnCode: "1",
};
}
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientDevelopRequest } from "../request/upsertClientDevelop";
import { UpsertClientDevelopResponse } from "../response/upsertClientDevelop";
export default async function upsertClientDevelop(
rep: Repositories,
req: UpsertClientDevelopRequest
): Promise<UpsertClientDevelopResponse> {
return {
returnCode: "1",
};
}
+12
View File
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientErrorRequest } from "../request/upsertClientError";
import { UpsertClientErrorResponse } from "../response/upsertClientError";
export default async function upsertClientError(
rep: Repositories,
req: UpsertClientErrorRequest
): Promise<UpsertClientErrorResponse> {
return {
returnCode: "1",
};
}
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientSettingRequest } from "../request/upsertClientSetting";
import { UpsertClientSettingResponse } from "../response/upsertClientSetting";
export default async function upsertClientSetting(
rep: Repositories,
req: UpsertClientSettingRequest
): Promise<UpsertClientSettingResponse> {
return {
returnCode: "1",
};
}
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientTestmodeRequest } from "../request/upsertClientTestmode";
import { UpsertClientTestmodeResponse } from "../response/upsertClientTestmode";
export default async function upsertClientTestmode(
rep: Repositories,
req: UpsertClientTestmodeRequest
): Promise<UpsertClientTestmodeResponse> {
return {
returnCode: "1",
};
}
+73
View File
@@ -0,0 +1,73 @@
import { Repositories } from "../repo";
import { readAimeId } from "../proto/base";
import { readUserCharacter } from "../proto/userCharacter";
import { readUserData } from "../proto/userData";
import { readUserGameOption } from "../proto/userGameOption";
import { UpsertUserAllRequest } from "../request/upsertUserAll";
import { UpsertUserAllResponse } from "../response/upsertUserAll";
import { readUserGameOptionEx } from "../proto/userGameOptionEx";
import { readUserMap } from "../proto/userMap";
import { readUserItem } from "../proto/userItem";
import { readUserMusicDetail } from "../proto/userMusic";
import { readUserActivity } from "../proto/userActivity";
import { readUserDataEx } from "../proto/userDataEx";
import { readUserPlaylog } from "../proto/userPlaylog";
// It shouldn't need to be said really, but seeing as this message (A) requires
// fairly lengthy processing and (B) is only sent right at the end of a credit,
// you should probably copy the JSON from the server's debug log and then
// validate this implementation using curl if you're planning to make any
// modifications to how things get saved.
export default async function upsertUserAll(
rep: Repositories,
req: UpsertUserAllRequest
): Promise<UpsertUserAllResponse> {
const aimeId = readAimeId(req.userId);
const payload = req.upsertUserAll;
const profile = readUserData(req.upsertUserAll.userData[0]);
const profileId = await rep.userData().save(aimeId, profile);
// Empty lists don't get sent at all. Not sure which are potentially empty
// so let's just assume the answer is "all of them". Hence the use of || []
// everywhere.
for (const item of payload.userGameOption || []) {
await rep.userGameOption().save(profileId, readUserGameOption(item));
}
for (const item of payload.userGameOptionEx || []) {
await rep.userGameOptionEx().save(profileId, readUserGameOptionEx(item));
}
for (const item of payload.userMapList || []) {
await rep.userMap().save(profileId, readUserMap(item));
}
for (const item of payload.userCharacterList || []) {
await rep.userCharacter().save(profileId, readUserCharacter(item));
}
for (const item of payload.userItemList || []) {
await rep.userItem().save(profileId, readUserItem(item));
}
for (const item of payload.userMusicDetailList || []) {
await rep.userMusic().save(profileId, readUserMusicDetail(item));
}
for (const item of payload.userActivityList || []) {
await rep.userActivity().save(profileId, readUserActivity(item));
}
for (const item of payload.userPlaylogList || []) {
await rep.userPlaylog().save(profileId, readUserPlaylog(item));
}
for (const item of payload.userDataEx || []) {
await rep.userDataEx().save(profileId, readUserDataEx(item));
}
return { returnCode: "1" };
}
+2 -75
View File
@@ -1,75 +1,2 @@
import compression from "compression";
import express from "express";
import logger from "debug";
import getGameCharge from "./getGameCharge";
import getGameEvent from "./getGameEvent";
import getGameIdlist from "./getGameIdlist";
import getGameMessage from "./getGameMessage";
import getGameRanking from "./getGameRanking";
import getGameSale from "./getGameSale";
import getGameSetting from "./getGameSetting";
import upsertClientBookkeeping from "./upsertClientBookkeeping";
import upsertClientDevelop from "./upsertClientDevelop";
import upsertClientError from "./upsertClientError";
import upsertClientSetting from "./upsertClientSetting";
import upsertClientTestmode from "./upsertClientTestmode";
const debug = logger("app:chuni:io");
const app = express();
// Thankfully we can use standard middleware for JSON I/O. We have to use a
// zlib middleware as well because compression is non-optional: the client will
// attempt to inflate whatever response we send to it whether there's a
// Transfer-Encoding header or not.
app.use(function(req, res, next) {
// Chunithm client does not follow the HTTP spec: user agents must indicate
// that they are willing to accept a particular Transfer-Encoding for that
// T-E to be used (unless the T-E in question is "chunked" but w/e), and this
// indication is advisory anyway, the server is permitted to ignore it.
//
// Force this header into the request, since it is not present normally.
req.headers["accept-encoding"] = "deflate";
return next();
});
app.use(compression({ threshold: 0 }));
app.use(express.json());
// Trace requests and responses
app.use(function(req, resp, next) {
debug("\n--- Chunithm %s ---\n", req.url);
debug("Request: %j", req.body);
const prevJson = resp.json;
resp.json = function(obj) {
debug("Response: %j", obj);
resp.json = prevJson;
resp.json.apply(this, arguments);
return resp;
};
next();
});
app.post("/ChuniServlet/GetGameChargeApi", getGameCharge);
app.post("/ChuniServlet/GetGameEventApi", getGameEvent);
app.post("/ChuniServlet/GetGameIdlistApi", getGameIdlist);
app.post("/ChuniServlet/GetGameMessageApi", getGameMessage);
app.post("/ChuniServlet/GetGameRankingApi", getGameRanking);
app.post("/ChuniServlet/GetGameSaleApi", getGameSale);
app.post("/ChuniServlet/GetGameSettingApi", getGameSetting);
app.post("/ChuniServlet/UpsertClientBookkeepingApi", upsertClientBookkeeping);
app.post("/ChuniServlet/UpsertClientDevelopApi", upsertClientDevelop);
app.post("/ChuniServlet/UpsertClientErrorApi", upsertClientError);
app.post("/ChuniServlet/UpsertClientSettingApi", upsertClientSetting);
app.post("/ChuniServlet/UpsertClientTestmodeApi", upsertClientTestmode);
export default app;
import chunithm from "./handler";
export default chunithm;
+10
View File
@@ -0,0 +1,10 @@
export interface GameChargeItem {
orderId: number;
chargeId: number;
price: number;
startDate: Date;
endDate: Date;
salePrice: number;
saleStartDate: Date;
saleEndDate: Date;
}
+6
View File
@@ -0,0 +1,6 @@
export interface GameEventItem {
type: number;
id: number;
startDate: Date;
endDate: Date;
}
+7
View File
@@ -0,0 +1,7 @@
export interface GameMessageItem {
type: number;
id: number;
message: string;
startDate: Date;
endDate: Date;
}
+4
View File
@@ -0,0 +1,4 @@
export interface GameRankingItem {
id: bigint;
point: bigint;
}
+8
View File
@@ -0,0 +1,8 @@
export interface GameSaleItem {
orderId: number;
type: number;
id: number;
rate: number;
startDate: Date;
endDate: Date;
}
+9
View File
@@ -0,0 +1,9 @@
export interface UserActivityItem {
kind: number;
activityId: number; // This is just called "id" in the JSON!
sortNumber: number;
param1: number;
param2: number;
param3: number;
param4: number;
}
+11
View File
@@ -0,0 +1,11 @@
export interface UserCharacterItem {
characterId: number;
playCount: number;
level: number;
skillId: number;
friendshipExp: number;
isValid: boolean;
isNewMark: boolean;
param1: number;
param2: number;
}
+66
View File
@@ -0,0 +1,66 @@
export interface UserDataItem {
/** NFC LUID */
accessCode: string;
/** Uses wide latin chars */
userName: string;
isWebJoin: boolean;
webLimitDate: Date;
level: number;
reincarnationNum: number;
exp: number;
point: number;
totalPoint: bigint;
playCount: number;
multiPlayCount: number;
multiWinCount: number;
requestResCount: number;
acceptResCount: number;
successResCount: number;
playerRating: number;
highestRating: number;
nameplateId: number;
frameId: number;
characterId: number;
trophyId: number;
playedTutorialBit: number;
firstTutorialCancelNum: number;
masterTutorialCancelNum: number;
totalRepertoireCount: number;
totalMapNum: number;
totalHiScore: bigint;
totalBasicHighScore: bigint;
totalAdvancedHighScore: bigint;
totalExpertHighScore: bigint;
totalMasterHighScore: bigint;
eventWatchedDate: Date;
friendCount: number;
isMaimai: boolean;
firstGameId: string;
firstRomVersion: string;
firstDataVersion: string;
firstPlayDate: Date;
lastGameId: string;
lastRomVersion: string;
lastDataVersion: string;
lastPlayDate: Date;
/** ALLNet place ID */
lastPlaceId: number;
/** ALLNet place name */
lastPlaceName: string;
/** ALLNet "region0" */
lastRegionId: string;
/** ALLNet "region_name0" */
lastRegionName: string;
/** ALLNet "allnet_id" string */
lastAllNetId: string;
/** Keychip ID */
lastClientId: string;
}
+36
View File
@@ -0,0 +1,36 @@
export interface UserDataExItem {
compatibleCmVersion: string;
medal: number;
mapIconId: number;
voiceId: number;
ext1: number;
ext2: number;
ext3: number;
ext4: number;
ext5: number;
ext6: number;
ext7: number;
ext8: number;
ext9: number;
ext10: number;
ext11: number;
ext12: number;
ext13: number;
ext14: number;
ext15: number;
ext16: number;
ext17: number;
ext18: number;
ext19: number;
ext20: number;
extStr1: string;
extStr2: string;
extStr3: string;
extStr4: string;
extStr5: string;
extLong1: bigint;
extLong2: bigint;
extLong3: bigint;
extLong4: bigint;
extLong5: bigint;
}
+24
View File
@@ -0,0 +1,24 @@
export interface UserGameOptionItem {
bgInfo: number;
fieldColor: number;
guideSound: number;
soundEffect: number;
guideLine: number;
speed: number;
optionSet: number;
matching: number;
judgePos: number;
rating: number;
judgeJustice: number;
judgeAttack: number;
headphone: number;
playerLevel: number;
successTap: number;
successExTap: number;
successSlideHold: number;
successAir: number;
successFlick: number;
successSkill: number;
successTapTimbre: number;
privacy: number;
}
+22
View File
@@ -0,0 +1,22 @@
export interface UserGameOptionExItem {
ext1: number;
ext2: number;
ext3: number;
ext4: number;
ext5: number;
ext6: number;
ext7: number;
ext8: number;
ext9: number;
ext10: number;
ext11: number;
ext12: number;
ext13: number;
ext14: number;
ext15: number;
ext16: number;
ext17: number;
ext18: number;
ext19: number;
ext20: number;
}
+6
View File
@@ -0,0 +1,6 @@
export interface UserItemItem {
itemKind: number;
itemId: number;
stock: number;
isValid: boolean;
}
+11
View File
@@ -0,0 +1,11 @@
export interface UserMapItem {
mapId: number;
position: number;
isClear: boolean;
areaId: number;
routeNumber: number;
eventId: number;
rate: number;
statusCount: number;
isValid: boolean;
}
+18
View File
@@ -0,0 +1,18 @@
export interface UserMusicDetailItem {
musicId: number;
level: number;
playCount: number;
scoreMax: number;
resRequestCount: number;
resAcceptCount: number;
resSuccessCount: number;
missCount: number;
maxComboCount: number;
isFullCombo: boolean;
isAllJustice: boolean;
isSuccess: boolean;
fullChain: number;
maxChain: number;
scoreRank: number;
isLock: boolean;
}
+52
View File
@@ -0,0 +1,52 @@
export interface UserPlaylogItem {
orderId: number;
sortNumber: number;
placeId: number;
playDate: Date;
userPlayDate: Date;
musicId: number;
level: number;
customId: number;
playedUserId1: number;
playedUserId2: number;
playedUserId3: number;
playedUserName1: string;
playedUserName2: string;
playedUserName3: string;
playedMusicLevel1: number;
playedMusicLevel2: number;
playedMusicLevel3: number;
playedCustom1: number;
playedCustom2: number;
playedCustom3: number;
track: number;
score: number;
rank: number;
maxCombo: number;
maxChain: number;
rateTap: number;
rateHold: number;
rateSlide: number;
rateAir: number;
rateFlick: number;
judgeGuilty: number;
judgeAttack: number;
judgeJustice: number;
judgeCritical: number;
eventId: number;
playerRating: number;
isNewRecord: boolean;
isFullCombo: boolean;
fullChainKind: number;
isAllJustice: boolean;
isContinue: boolean;
isFreeToPlay: boolean;
characterId: number;
skillId: number;
playKind: number;
isClear: boolean;
skillLevel: number;
skillEffect: number;
placeName: string;
isMaimai: boolean;
}
+6
View File
@@ -0,0 +1,6 @@
export interface UserRecentRatingItem {
musicId: number;
difficultId: number;
romVersionCode: number;
score: number;
}
+5
View File
@@ -0,0 +1,5 @@
export interface UserRegionItem {
regionId: number;
playCount: number;
created: Date;
}
+60
View File
@@ -0,0 +1,60 @@
import format from "date-fns/format";
import { AimeId } from "../../model";
type ModelObject<T> = {
[K in keyof T]: string | number | bigint | boolean | Date;
};
export type Crush<T extends ModelObject<T>> = {
[K in keyof T]: string;
};
export function readAimeId(str: string): AimeId {
return parseInt(str) as AimeId;
}
export function readBoolean(str: string): boolean {
// The compiler will catch typos of "readBoolean". It will not catch typos
// of "true". Which sounds like a few hours of debugging I'd prefer to
// proactively avoid.
return str === "true";
}
export function readDate(str: string): Date {
// If we ever need to change this then we'll be able to do it from one place
return new Date(str);
}
export function readWtf8(src: string): string {
// If you read this aloud it is pronounced "Double UTF-8".
// Which actually kind of accurately describes what's going on here.
const buf = Buffer.alloc(src.length);
for (let i = 0; i < src.length; i++) {
buf.writeUInt8(src.codePointAt(i)!, i);
}
return buf.toString("utf-8");
}
export function writeDate(date: Date): string {
return format(date, "YYYY-MM-DD HH:mm:ss");
}
export function writeObject<T extends ModelObject<T>>(obj: T): Crush<T> {
const result = {} as Crush<T>;
for (const k in obj) {
const v = obj[k];
if (v instanceof Date) {
result[k] = writeDate(v);
} else {
result[k] = v.toString();
}
}
return result;
}
+8
View File
@@ -0,0 +1,8 @@
import { Crush, writeObject } from "./base";
import { GameChargeItem } from "../model/gameCharge";
export type GameChargeJson = Crush<GameChargeItem>;
export function writeGameCharge(obj: GameChargeItem): GameChargeJson {
return writeObject(obj);
}
+8
View File
@@ -0,0 +1,8 @@
import { Crush, writeObject } from "./base";
import { GameEventItem } from "../model/gameEvent";
export type GameEventJson = Crush<GameEventItem>;
export function writeGameEvent(obj: GameEventItem): GameEventJson {
return writeObject(obj);
}
+8
View File
@@ -0,0 +1,8 @@
import { Crush, writeObject } from "./base";
import { GameMessageItem } from "../model/gameMessage";
export type GameMessageJson = Crush<GameMessageItem>;
export function writeGameMessage(obj: GameMessageItem): GameMessageJson {
return writeObject(obj);
}
+8
View File
@@ -0,0 +1,8 @@
import { Crush, writeObject } from "./base";
import { GameRankingItem } from "../model/gameRanking";
export type GameRankingJson = Crush<GameRankingItem>;
export function writeGameRanking(obj: GameRankingItem): GameRankingJson {
return writeObject(obj);
}
+8
View File
@@ -0,0 +1,8 @@
import { Crush, writeObject } from "./base";
import { GameSaleItem } from "../model/gameSale";
export type GameSaleJson = Crush<GameSaleItem>;
export function writeGameSale(obj: GameSaleItem): GameSaleJson {
return writeObject(obj);
}
+39
View File
@@ -0,0 +1,39 @@
import { UserActivityItem } from "../model/userActivity";
export interface UserActivityJson {
// Not quite the same as our internal representation! Internally we refer to
// the "id" field as "activityId", because the SQL table that stores these
// values already has an "id" column that holds a synthetic primary key.
kind: string;
id: string;
sortNumber: string;
param1: string;
param2: string;
param3: string;
param4: string;
}
export function readUserActivity(json: UserActivityJson): UserActivityItem {
return {
kind: parseInt(json.kind),
activityId: parseInt(json.id), // <-- Look closely!
sortNumber: parseInt(json.sortNumber),
param1: parseInt(json.param1),
param2: parseInt(json.param2),
param3: parseInt(json.param3),
param4: parseInt(json.param4),
};
}
export function writeUserActivity(obj: UserActivityItem): UserActivityJson {
return {
kind: obj.kind.toString(),
id: obj.activityId.toString(), // <-- Look closely!
sortNumber: obj.sortNumber.toString(),
param1: obj.param1.toString(),
param2: obj.param2.toString(),
param3: obj.param3.toString(),
param4: obj.param4.toString(),
};
}
+22
View File
@@ -0,0 +1,22 @@
import { Crush, readBoolean, writeObject } from "./base";
import { UserCharacterItem } from "../model/userCharacter";
export type UserCharacterJson = Crush<UserCharacterItem>;
export function readUserCharacter(json: UserCharacterJson): UserCharacterItem {
return {
characterId: parseInt(json.characterId),
playCount: parseInt(json.playCount),
level: parseInt(json.level),
skillId: parseInt(json.skillId),
friendshipExp: parseInt(json.friendshipExp),
isValid: readBoolean(json.isValid),
isNewMark: readBoolean(json.isNewMark),
param1: parseInt(json.param1),
param2: parseInt(json.param2),
};
}
export function writeUserCharacter(obj: UserCharacterItem): UserCharacterJson {
return writeObject(obj);
}
+68
View File
@@ -0,0 +1,68 @@
import { Crush, readBoolean, readDate, readWtf8, writeObject } from "./base";
import { UserDataItem } from "../model/userData";
export type UserDataJson = Crush<UserDataItem>;
export function readUserData(json: UserDataJson): UserDataItem {
return {
accessCode: json.accessCode,
userName: readWtf8(json.userName), // <--
isWebJoin: readBoolean(json.isWebJoin),
webLimitDate: readDate(json.webLimitDate),
level: parseInt(json.level),
reincarnationNum: parseInt(json.reincarnationNum),
exp: parseInt(json.exp),
point: parseInt(json.point),
totalPoint: BigInt(json.totalPoint),
playCount: parseInt(json.playCount),
multiPlayCount: parseInt(json.multiPlayCount),
multiWinCount: parseInt(json.multiWinCount),
requestResCount: parseInt(json.requestResCount),
acceptResCount: parseInt(json.acceptResCount),
successResCount: parseInt(json.successResCount),
playerRating: parseInt(json.playerRating),
highestRating: parseInt(json.highestRating),
nameplateId: parseInt(json.nameplateId),
frameId: parseInt(json.frameId),
characterId: parseInt(json.characterId),
trophyId: parseInt(json.trophyId),
playedTutorialBit: parseInt(json.playedTutorialBit),
firstTutorialCancelNum: parseInt(json.firstTutorialCancelNum),
masterTutorialCancelNum: parseInt(json.masterTutorialCancelNum),
totalRepertoireCount: parseInt(json.totalRepertoireCount),
totalMapNum: parseInt(json.totalMapNum),
totalHiScore: BigInt(json.totalHiScore),
totalBasicHighScore: BigInt(json.totalBasicHighScore),
totalAdvancedHighScore: BigInt(json.totalAdvancedHighScore),
totalExpertHighScore: BigInt(json.totalExpertHighScore),
totalMasterHighScore: BigInt(json.totalMasterHighScore),
eventWatchedDate: readDate(json.eventWatchedDate),
friendCount: parseInt(json.friendCount),
isMaimai: readBoolean(json.isMaimai),
firstGameId: json.firstGameId,
firstRomVersion: json.firstRomVersion,
firstDataVersion: json.firstDataVersion,
firstPlayDate: readDate(json.firstPlayDate),
lastGameId: json.lastGameId,
lastRomVersion: json.lastRomVersion,
lastDataVersion: json.lastDataVersion,
lastPlayDate: readDate(json.lastPlayDate),
lastPlaceId: parseInt(json.lastPlaceId),
lastPlaceName: json.lastPlaceName,
lastRegionId: json.lastRegionId,
lastRegionName: json.lastRegionName,
lastAllNetId: json.lastAllNetId,
lastClientId: json.lastClientId,
};
}
export function writeUserData(obj: UserDataItem): UserDataJson {
// Note: no need for a writeWtf8() for the user name.
// Seems like something went very horribly wrong with their use of the
// boost::property_page JSON codec, but C++ and especially Boost is a hot
// mess at the best of times, so the Chunithm dev team has my sympathies
// nonetheless.
return writeObject(obj);
}
+47
View File
@@ -0,0 +1,47 @@
import { Crush, writeObject } from "./base";
import { UserDataExItem } from "../model/userDataEx";
export type UserDataExJson = Crush<UserDataExItem>;
export function readUserDataEx(json: UserDataExJson): UserDataExItem {
return {
compatibleCmVersion: json.compatibleCmVersion,
medal: parseInt(json.medal),
mapIconId: parseInt(json.mapIconId),
voiceId: parseInt(json.voiceId),
ext1: parseInt(json.ext1),
ext2: parseInt(json.ext2),
ext3: parseInt(json.ext3),
ext4: parseInt(json.ext4),
ext5: parseInt(json.ext5),
ext6: parseInt(json.ext6),
ext7: parseInt(json.ext7),
ext8: parseInt(json.ext8),
ext9: parseInt(json.ext9),
ext10: parseInt(json.ext10),
ext11: parseInt(json.ext11),
ext12: parseInt(json.ext12),
ext13: parseInt(json.ext13),
ext14: parseInt(json.ext14),
ext15: parseInt(json.ext15),
ext16: parseInt(json.ext16),
ext17: parseInt(json.ext17),
ext18: parseInt(json.ext18),
ext19: parseInt(json.ext19),
ext20: parseInt(json.ext20),
extStr1: json.extStr1,
extStr2: json.extStr2,
extStr3: json.extStr3,
extStr4: json.extStr4,
extStr5: json.extStr5,
extLong1: BigInt(json.extLong1),
extLong2: BigInt(json.extLong2),
extLong3: BigInt(json.extLong3),
extLong4: BigInt(json.extLong4),
extLong5: BigInt(json.extLong5),
};
}
export function writeUserDataEx(obj: UserDataExItem): UserDataExJson {
return writeObject(obj);
}
+39
View File
@@ -0,0 +1,39 @@
import { Crush, writeObject } from "./base";
import { UserGameOptionItem } from "../model/userGameOption";
export type UserGameOptionJson = Crush<UserGameOptionItem>;
export function readUserGameOption(
json: UserGameOptionJson
): UserGameOptionItem {
return {
bgInfo: parseInt(json.bgInfo),
fieldColor: parseInt(json.fieldColor),
guideSound: parseInt(json.guideSound),
soundEffect: parseInt(json.soundEffect),
guideLine: parseInt(json.guideLine),
speed: parseInt(json.speed),
optionSet: parseInt(json.optionSet),
matching: parseInt(json.matching),
judgePos: parseInt(json.judgePos),
rating: parseInt(json.rating),
judgeJustice: parseInt(json.judgeJustice),
judgeAttack: parseInt(json.judgeAttack),
headphone: parseInt(json.headphone),
playerLevel: parseInt(json.playerLevel),
successTap: parseInt(json.successTap),
successExTap: parseInt(json.successExTap),
successSlideHold: parseInt(json.successSlideHold),
successAir: parseInt(json.successAir),
successFlick: parseInt(json.successFlick),
successSkill: parseInt(json.successSkill),
successTapTimbre: parseInt(json.successTapTimbre),
privacy: parseInt(json.privacy),
};
}
export function writeUserGameOption(
obj: UserGameOptionItem
): UserGameOptionJson {
return writeObject(obj);
}
+37
View File
@@ -0,0 +1,37 @@
import { Crush, writeObject } from "./base";
import { UserGameOptionExItem } from "../model/userGameOptionEx";
export type UserGameOptionExJson = Crush<UserGameOptionExItem>;
export function readUserGameOptionEx(
json: UserGameOptionExJson
): UserGameOptionExItem {
return {
ext1: parseInt(json.ext1),
ext2: parseInt(json.ext2),
ext3: parseInt(json.ext3),
ext4: parseInt(json.ext4),
ext5: parseInt(json.ext5),
ext6: parseInt(json.ext6),
ext7: parseInt(json.ext7),
ext8: parseInt(json.ext8),
ext9: parseInt(json.ext9),
ext10: parseInt(json.ext10),
ext11: parseInt(json.ext11),
ext12: parseInt(json.ext12),
ext13: parseInt(json.ext13),
ext14: parseInt(json.ext14),
ext15: parseInt(json.ext15),
ext16: parseInt(json.ext16),
ext17: parseInt(json.ext17),
ext18: parseInt(json.ext18),
ext19: parseInt(json.ext19),
ext20: parseInt(json.ext20),
};
}
export function writeUserGameOptionEx(
obj: UserGameOptionExItem
): UserGameOptionExJson {
return writeObject(obj);
}
+17
View File
@@ -0,0 +1,17 @@
import { Crush, readBoolean, writeObject } from "./base";
import { UserItemItem } from "../model/userItem";
export type UserItemJson = Crush<UserItemItem>;
export function readUserItem(json: UserItemJson): UserItemItem {
return {
itemKind: parseInt(json.itemKind),
itemId: parseInt(json.itemId),
stock: parseInt(json.stock),
isValid: readBoolean(json.isValid),
};
}
export function writeUserItem(obj: UserItemItem): UserItemJson {
return writeObject(obj);
}
+22
View File
@@ -0,0 +1,22 @@
import { Crush, readBoolean, writeObject } from "./base";
import { UserMapItem } from "../model/userMap";
export type UserMapJson = Crush<UserMapItem>;
export function readUserMap(json: UserMapJson): UserMapItem {
return {
mapId: parseInt(json.mapId),
position: parseInt(json.position),
isClear: readBoolean(json.isClear),
areaId: parseInt(json.areaId),
routeNumber: parseInt(json.routeNumber),
eventId: parseInt(json.eventId),
rate: parseInt(json.rate),
statusCount: parseInt(json.statusCount),
isValid: readBoolean(json.isValid),
};
}
export function writeUserMap(obj: UserMapItem): UserMapJson {
return writeObject(obj);
}
+49
View File
@@ -0,0 +1,49 @@
import { Crush, readBoolean, writeObject } from "./base";
import { UserMusicDetailItem } from "../model/userMusic";
/**
* Chart high score object. Saved as a flat list when the client saves new
* scores, loaded in groups when a player's high scores are loaded.
*/
export type UserMusicDetailJson = Crush<UserMusicDetailItem>;
/**
* Grouping of `UserMusicDetailItem` objects, used when scores are loaded.
* Scores are grouped by `musicId`.
*/
export interface UserMusicJson {
/** Integer, number of sub-objects in this object */
length: string;
/** Scores for this music ID */
userMusicDetailList: UserMusicDetailJson[];
}
export function readUserMusicDetail(
json: UserMusicDetailJson
): UserMusicDetailItem {
return {
musicId: parseInt(json.musicId),
level: parseInt(json.level),
playCount: parseInt(json.playCount),
scoreMax: parseInt(json.scoreMax),
resRequestCount: parseInt(json.resRequestCount),
resAcceptCount: parseInt(json.resAcceptCount),
resSuccessCount: parseInt(json.resSuccessCount),
missCount: parseInt(json.missCount),
maxComboCount: parseInt(json.maxComboCount),
isFullCombo: readBoolean(json.isFullCombo),
isAllJustice: readBoolean(json.isAllJustice),
isSuccess: readBoolean(json.isSuccess),
fullChain: parseInt(json.fullChain),
maxChain: parseInt(json.maxChain),
scoreRank: parseInt(json.scoreRank),
isLock: readBoolean(json.isLock),
};
}
export function writeUserMusicDetail(
obj: UserMusicDetailItem
): UserMusicDetailJson {
return writeObject(obj);
}
+59
View File
@@ -0,0 +1,59 @@
import { Crush, readBoolean, readDate } from "./base";
import { UserPlaylogItem } from "../model/userPlaylog";
export type UserPlaylogJson = Crush<UserPlaylogItem>;
export function readUserPlaylog(json: UserPlaylogJson): UserPlaylogItem {
return {
orderId: parseInt(json.orderId),
sortNumber: parseInt(json.sortNumber),
placeId: parseInt(json.placeId),
playDate: readDate(json.playDate),
userPlayDate: readDate(json.userPlayDate),
musicId: parseInt(json.musicId),
level: parseInt(json.level),
customId: parseInt(json.customId),
playedUserId1: parseInt(json.playedUserId1),
playedUserId2: parseInt(json.playedUserId2),
playedUserId3: parseInt(json.playedUserId3),
playedUserName1: json.playedUserName1,
playedUserName2: json.playedUserName2,
playedUserName3: json.playedUserName3,
playedMusicLevel1: parseInt(json.playedMusicLevel1),
playedMusicLevel2: parseInt(json.playedMusicLevel2),
playedMusicLevel3: parseInt(json.playedMusicLevel3),
playedCustom1: parseInt(json.playedCustom1),
playedCustom2: parseInt(json.playedCustom2),
playedCustom3: parseInt(json.playedCustom3),
track: parseInt(json.track),
score: parseInt(json.score),
rank: parseInt(json.rank),
maxCombo: parseInt(json.maxCombo),
maxChain: parseInt(json.maxChain),
rateTap: parseInt(json.rateTap),
rateHold: parseInt(json.rateHold),
rateSlide: parseInt(json.rateSlide),
rateAir: parseInt(json.rateAir),
rateFlick: parseInt(json.rateFlick),
judgeGuilty: parseInt(json.judgeGuilty),
judgeAttack: parseInt(json.judgeAttack),
judgeJustice: parseInt(json.judgeJustice),
judgeCritical: parseInt(json.judgeCritical),
eventId: parseInt(json.eventId),
playerRating: parseInt(json.playerRating),
isNewRecord: readBoolean(json.isNewRecord),
isFullCombo: readBoolean(json.isFullCombo),
fullChainKind: parseInt(json.fullChainKind),
isAllJustice: readBoolean(json.isAllJustice),
isContinue: readBoolean(json.isContinue),
isFreeToPlay: readBoolean(json.isFreeToPlay),
characterId: parseInt(json.characterId),
skillId: parseInt(json.skillId),
playKind: parseInt(json.playKind),
isClear: readBoolean(json.isClear),
skillLevel: parseInt(json.skillLevel),
skillEffect: parseInt(json.skillEffect),
placeName: json.placeName,
isMaimai: readBoolean(json.isMaimai),
};
}
+21
View File
@@ -0,0 +1,21 @@
import { Crush, writeObject } from "./base";
import { UserRecentRatingItem } from "../model/userRecentRating";
export type UserRecentRatingJson = Crush<UserRecentRatingItem>;
export function readUserRecentRating(
json: UserRecentRatingJson
): UserRecentRatingItem {
return {
musicId: parseInt(json.musicId),
difficultId: parseInt(json.difficultId),
romVersionCode: parseInt(json.romVersionCode),
score: parseInt(json.score),
};
}
export function writeUserRecentRating(
obj: UserRecentRatingItem
): UserRecentRatingJson {
return writeObject(obj);
}
+16
View File
@@ -0,0 +1,16 @@
import { Crush, readDate, writeObject } from "./base";
import { UserRegionItem } from "../model/userRegion";
export type UserRegionJson = Crush<UserRegionItem>;
export function readUserRegion(json: UserRegionJson): UserRegionItem {
return {
regionId: parseInt(json.regionId),
playCount: parseInt(json.playCount),
created: readDate(json.created),
};
}
export function writeUserRegion(obj: UserRegionItem): UserRegionJson {
return writeObject(obj);
}
+19
View File
@@ -0,0 +1,19 @@
import { UserDataItem } from "../model/userData";
import { Id } from "../../model";
export interface Page {
offset: number;
limit: number;
}
export interface Repository<T> {
save(profileId: Id<UserDataItem>, obj: T): Promise<void>;
}
export interface Repository1<T> extends Repository<T> {
load(profileId: Id<UserDataItem>): Promise<T>;
}
export interface RepositoryN<T> extends Repository<T> {
load(profileId: Id<UserDataItem>, page?: Page): Promise<T[]>;
}
+34
View File
@@ -0,0 +1,34 @@
export { Page } from "./_defs";
import { UserActivityRepository } from "./userActivity";
import { UserCharacterRepository } from "./userCharacter";
import { UserDataRepository } from "./userData";
import { UserDataExRepository } from "./userDataEx";
import { UserGameOptionRepository } from "./userGameOption";
import { UserGameOptionExRepository } from "./userGameOptionEx";
import { UserItemRepository } from "./userItem";
import { UserMapRepository } from "./userMap";
import { UserMusicRepository } from "./userMusic";
import { UserPlaylogRepository } from "./userPlaylog";
export interface Repositories {
userActivity(): UserActivityRepository;
userCharacter(): UserCharacterRepository;
userData(): UserDataRepository;
userDataEx(): UserDataExRepository;
userGameOption(): UserGameOptionRepository;
userGameOptionEx(): UserGameOptionExRepository;
userItem(): UserItemRepository;
userMap(): UserMapRepository;
userMusic(): UserMusicRepository;
userPlaylog(): UserPlaylogRepository;
}
+9
View File
@@ -0,0 +1,9 @@
import { UserActivityItem } from "../model/userActivity";
import { UserDataItem } from "../model/userData";
import { Id } from "../../model";
export interface UserActivityRepository {
load(profileId: Id<UserDataItem>, kind: number): Promise<UserActivityItem[]>;
save(profileId: Id<UserDataItem>, obj: UserActivityItem): Promise<void>;
}
+12
View File
@@ -0,0 +1,12 @@
import { RepositoryN } from "./_defs";
import { UserCharacterItem } from "../model/userCharacter";
import { UserDataItem } from "../model/userData";
import { Id } from "../../model";
export interface UserCharacterRepository
extends RepositoryN<UserCharacterItem> {
loadOne(
profileId: Id<UserDataItem>,
characterId: number
): Promise<UserCharacterItem>;
}
+12
View File
@@ -0,0 +1,12 @@
import { UserDataItem } from "../model/userData";
import { AimeId, Id } from "../../model";
export interface UserDataRepository {
load(profileId: Id<UserDataItem>): Promise<UserDataItem>;
lookup(aimeId: AimeId): Promise<Id<UserDataItem>>;
save(aimeId: AimeId, obj: UserDataItem): Promise<Id<UserDataItem>>;
tryLookup(aimeId: AimeId): Promise<Id<UserDataItem> | undefined>;
}
+4
View File
@@ -0,0 +1,4 @@
import { Repository1 } from "./_defs";
import { UserDataExItem } from "../model/userDataEx";
export type UserDataExRepository = Repository1<UserDataExItem>;
+4
View File
@@ -0,0 +1,4 @@
import { Repository1 } from "./_defs";
import { UserGameOptionItem } from "../model/userGameOption";
export type UserGameOptionRepository = Repository1<UserGameOptionItem>;
+4
View File
@@ -0,0 +1,4 @@
import { Repository1 } from "./_defs";
import { UserGameOptionExItem } from "../model/userGameOptionEx";
export type UserGameOptionExRepository = Repository1<UserGameOptionExItem>;
+14
View File
@@ -0,0 +1,14 @@
import { Page } from "./_defs";
import { UserDataItem } from "../model/userData";
import { UserItemItem } from "../model/userItem";
import { Id } from "../../model";
export interface UserItemRepository {
load(
profileId: Id<UserDataItem>,
itemKind: number,
page: Page
): Promise<UserItemItem[]>;
save(profileId: Id<UserDataItem>, obj: UserItemItem): Promise<void>;
}
+4
View File
@@ -0,0 +1,4 @@
import { RepositoryN } from "./_defs";
import { UserMapItem } from "../model/userMap";
export type UserMapRepository = RepositoryN<UserMapItem>;
+4
View File
@@ -0,0 +1,4 @@
import { RepositoryN } from "./_defs";
import { UserMusicDetailItem } from "../model/userMusic";
export type UserMusicRepository = RepositoryN<UserMusicDetailItem>;
+10
View File
@@ -0,0 +1,10 @@
import { UserDataItem } from "../model/userData";
import { UserPlaylogItem } from "../model/userPlaylog";
import { Id } from "../../model";
export interface UserPlaylogRepository {
// This seems to only be used internally by SEGA for analytics, but the data
// is nice to have.
save(profileId: Id<UserDataItem>, obj: UserPlaylogItem): Promise<void>;
}
+4
View File
@@ -0,0 +1,4 @@
export interface GameLoginRequest {
/** Integer, AiMe ID */
userId: string;
}
+4
View File
@@ -0,0 +1,4 @@
export interface GameLogoutRequest {
/** Integer, AiMe ID */
userId: string;
}
+4
View File
@@ -0,0 +1,4 @@
export interface GetGameChargeRequest {
/** Boolean */
isAllSale: "string";
}
+4
View File
@@ -0,0 +1,4 @@
export interface GetGameEventRequest {
// FIXME
type: string;
}
+4
View File
@@ -0,0 +1,4 @@
export interface GetGameIdlistRequest {
/** Integer */
type: string;
}
+7
View File
@@ -0,0 +1,7 @@
export interface GetGameMessageRequest {
/** Integer */
type: string;
/** Boolean */
isAllMessage: string;
}
+4
View File
@@ -0,0 +1,4 @@
export interface GetGameRankingRequest {
/** Long */
type: string;
}
+7
View File
@@ -0,0 +1,7 @@
export interface GetGameSaleRequest {
/** Integer */
type: string;
/** Boolean */
isAllSale: string;
}
+7
View File
@@ -0,0 +1,7 @@
export interface GetGameSettingRequest {
/** Base-10 ALLNet location ID */
placeId: string;
/** Keychip ID */
clientId: string;
}
+7
View File
@@ -0,0 +1,7 @@
export interface GetUserActivityRequest {
/** Integer, AiMe ID */
userId: string;
/** Integer, filter on `UserActivityItem.kind`. */
kind: string;
}

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