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:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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" };
|
||||
}
|
||||
@@ -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" };
|
||||
}
|
||||
@@ -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: [],
|
||||
};
|
||||
}
|
||||
@@ -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: [],
|
||||
};
|
||||
}
|
||||
@@ -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: [],
|
||||
};
|
||||
}
|
||||
@@ -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: [],
|
||||
};
|
||||
}
|
||||
@@ -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: [],
|
||||
};
|
||||
}
|
||||
@@ -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: [],
|
||||
};
|
||||
}
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -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: [],
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -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: [],
|
||||
};
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -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: [],
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
@@ -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" };
|
||||
}
|
||||
Reference in New Issue
Block a user