Save Time Attack records

This commit is contained in:
Tau
2019-11-06 17:28:29 -05:00
parent 4390935b6c
commit 3d166abee4
23 changed files with 185 additions and 92 deletions
-28
View File
@@ -1,28 +0,0 @@
import { RequestCode } from "./_defs";
import { CheckRankRequest } from "../request/checkRank";
checkRank.msgCode = 0x00cd as RequestCode;
checkRank.msgLen = 0x0080;
export function checkRank(buf: Buffer): CheckRankRequest {
return {
type: "check_rank_req",
field_0002: buf.readUInt16LE(0x0002),
field_0004: buf.readUInt32LE(0x0004),
field_0008: buf.readUInt32LE(0x0008),
field_000C: buf.readUInt16LE(0x000c),
field_0012: buf.readUInt8(0x0012),
field_0015: buf.readUInt8(0x0015),
field_0018: buf.readUInt32LE(0x0018),
field_0024: buf.readUInt32LE(0x0024),
field_0028: buf.readUInt32LE(0x0028),
field_002C: buf.readUInt32LE(0x002c),
field_0054: buf.readUInt8(0x0054),
field_0058: buf.readUInt32LE(0x0058),
field_005C: buf.readUInt8(0x005c),
field_005D: buf.readUInt8(0x005d),
field_005E: buf.readUInt16LE(0x005e),
field_0060: buf.readUInt16LE(0x0060),
field_0062: buf.readUInt8(0x0062),
};
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { Transform } from "stream";
import { checkRank } from "./checkRank";
import { saveTimeAttack } from "./saveTimeAttack";
import { createProfile } from "./createProfile";
import { createTeam } from "./createTeam";
import { discoverProfile } from "./discoverProfile";
@@ -41,7 +41,6 @@ export type ReaderFn = ((buf: Buffer) => Request) & {
};
const funcList: ReaderFn[] = [
checkRank,
createProfile,
createTeam,
discoverProfile,
@@ -66,6 +65,7 @@ const funcList: ReaderFn[] = [
saveProfile,
saveSettings,
saveStocker,
saveTimeAttack,
saveTopic,
unlockProfile,
updateProvisionalStoreRank,
+35
View File
@@ -0,0 +1,35 @@
import { RequestCode } from "./_defs";
import { Id } from "../model/base";
import { Profile } from "../model/profile";
import { SaveTimeAttackRequest } from "../request/saveTimeAttack";
saveTimeAttack.msgCode = 0x00cd as RequestCode;
saveTimeAttack.msgLen = 0x0080;
export function saveTimeAttack(buf: Buffer): SaveTimeAttackRequest {
return {
type: "save_time_attack_req",
profileId: buf.readUInt32LE(0x0004) as Id<Profile>,
dayNight: buf.readUInt8(0x0054) & 1,
payload: {
courseId: buf.readUInt8(0x0054) >> 1,
timestamp: new Date(buf.readUInt32LE(0x0058) * 1000).toISOString(), //hck
flags: buf.readUInt8(0x005c),
totalMsec: buf.readUInt32LE(0x0018),
stageMsec: [
buf.readUInt32LE(0x0024),
buf.readUInt32LE(0x0028),
buf.readUInt32LE(0x002c),
],
},
field_0002: buf.readUInt16LE(0x0002),
field_0008: buf.readUInt32LE(0x0008),
field_000C: buf.readUInt16LE(0x000c),
field_0012: buf.readUInt8(0x0012),
field_0015: buf.readUInt8(0x0015),
field_005D: buf.readUInt8(0x005d),
field_005E: buf.readUInt16LE(0x005e),
field_0060: buf.readUInt16LE(0x0060),
field_0062: buf.readUInt8(0x0062),
};
}
-9
View File
@@ -1,9 +0,0 @@
import { CheckRankResponse } from "../response/checkRank";
export function checkRank(res: CheckRankResponse): Buffer {
const buf = Buffer.alloc(0x00b0);
buf.writeUInt16LE(0x00ce, 0x0000);
return buf;
}
+4 -4
View File
@@ -1,7 +1,6 @@
import { Transform } from "stream";
import { _team } from "./_team";
import { checkRank } from "./checkRank";
import { discoverProfile } from "./discoverProfile";
import { generic } from "./generic";
import { lockProfile } from "./lockProfile";
@@ -19,6 +18,7 @@ import { loadTeamRanking } from "./loadTeamRanking";
import { loadTopTen } from "./loadTopTen";
import { saveExpedition } from "./saveExpedition";
import { saveGarage } from "./saveGarage";
import { saveTimeAttack } from "./saveTimeAttack";
import { saveTopic } from "./saveTopic";
import { unlockProfile } from "./unlockProfile";
import { updateProvisionalStoreRank } from "./updateProvisionalStoreRank";
@@ -27,9 +27,6 @@ import { Response } from "../response";
function encode(res: Response): Buffer {
switch (res.type) {
case "check_rank_res":
return checkRank(res);
case "create_team_res":
return _team(res);
@@ -87,6 +84,9 @@ function encode(res: Response): Buffer {
case "save_garage_res":
return saveGarage(res);
case "save_time_attack_res":
return saveTimeAttack(res);
case "unlock_profile_res":
return unlockProfile(res);
+42 -6
View File
@@ -23,12 +23,48 @@ export function loadProfile2(res: LoadProfileResponse2) {
//buf.fill(0xff, 0x0000, 0x08f4);
//buf.fill(0xff, 0x0c20, 0x0c2a);
// B0-C0: Cup and gauges
// B8-C0: All gauges, no cup
buf.fill(0xff, 0x00b4, 0x00b6);
buf.writeUInt8(0xff, 0x00b4); // PAPER CUP
buf.writeUInt16LE(0xffff, 0x00b8);
buf.writeUInt16LE(0x0009, 0x01ec); // MUSIC UNLOCKS
// Lake Akina CCW
/*
buf.writeUInt32LE(1555012398, 0x00e4); // Epoch sec?
buf.writeUInt16LE(5678, 0x0164); // National rank
buf.writeUInt32LE(146570, 0x04e0); // Time (msec)
buf.writeUInt8(5, 0x05a0); // Flags (Meaning unknown)
buf.writeUInt16LE(16010, 0x05c0); // Checkpoint msec 0 (div by 4)
buf.writeUInt16LE(16020, 0x05c2); // Checkpoint msec 1 (div by 4)
buf.writeUInt16LE(16030, 0x05c4); // Checkpoint msec 1 (div by 4)
// Lake Akina CW
buf.writeUInt32LE(1555012398, 0x00e4 + 4); // Epoch sec?
buf.writeUInt16LE(5678, 0x0164 + 2); // National rank
buf.writeUInt32LE(145072, 0x04e0 + 4); // Time (msec)
buf.writeUInt8(5, 0x05a0 + 1); // Flags (Meaning unknown)
// Akina DH
buf.writeUInt32LE(1555012398, 0x00e4 + 24); // Epoch sec?
buf.writeUInt16LE(5678, 0x0164 + 12); // National rank
buf.writeUInt32LE(222736, 0x04e0 + 24); // Time (msec)
buf.writeUInt8(5, 0x05a0 + 6); // Flags (Meaning unknown)
*/
for (const course of res.timeAttack.courses) {
const { courseId } = course;
buf.writeUInt32LE(
(new Date(course.timestamp).getTime() / 1000) | 0, // Date ctor hack
0x00e4 + courseId * 4
);
buf.writeUInt16LE(0xffff, 0x0164 + 2 * courseId); // National rank
buf.writeUInt32LE(course.totalMsec, 0x04e0 + 4 * courseId);
buf.writeUInt8(course.flags, 0x05a0 + courseId);
for (let i = 0; i < 3; i++) {
buf.writeUInt16LE(
course.stageMsec[i] >> 2,
0x05c0 + 6 * courseId + 2 * i
);
}
}
for (let i = 0; i < 9 && i < res.story.rows.length; i++) {
const row = res.story.rows[i];
+9
View File
@@ -0,0 +1,9 @@
import { SaveTimeAttackResponse } from "../response/saveTimeAttack";
export function saveTimeAttack(res: SaveTimeAttackResponse): Buffer {
const buf = Buffer.alloc(0x00b0);
buf.writeUInt16LE(0x00ce, 0x0000);
return buf;
}
-9
View File
@@ -1,9 +0,0 @@
import { CheckRankRequest } from "../request/checkRank";
import { CheckRankResponse } from "../response/checkRank";
import { World } from "../world";
export function checkRank(w: World, req: CheckRankRequest): CheckRankResponse {
return {
type: "check_rank_res",
};
}
+5
View File
@@ -4,6 +4,7 @@ import { Profile } from "../model/profile";
import { Settings } from "../model/settings";
import { Story } from "../model/story";
import { Team } from "../model/team";
import { TimeAttackState } from "../model/timeAttack";
import { Unlocks } from "../model/unlocks";
import { CreateProfileRequest } from "../request/createProfile";
import { GenericResponse } from "../response/generic";
@@ -42,6 +43,8 @@ export async function createProfile(
const story: Story = { x: 0, y: 0, rows: [] };
const timeAttack: TimeAttackState = { courses: [] };
const unlocks: Unlocks = { cup: 0, gauges: 0, music: 0 };
await Promise.all([
@@ -50,6 +53,8 @@ export async function createProfile(
w.car().save(profile.id, req.car),
w.missions().save(profile.id, missions),
w.settings().save(profile.id, settings),
w.story().save(profile.id, story),
w.timeAttack().save(profile.id, timeAttack),
w.unlocks().save(profile.id, unlocks),
]);
+4 -4
View File
@@ -1,5 +1,5 @@
import { _team } from "./_team";
import { checkRank } from "./checkRank";
import { saveTimeAttack } from "./saveTimeAttack";
import { createProfile } from "./createProfile";
import { discoverProfile } from "./discoverProfile";
import { load2on2 } from "./load2on2";
@@ -35,9 +35,6 @@ import { World } from "../world";
export async function dispatch(w: World, req: Request): Promise<Response> {
switch (req.type) {
case "check_rank_req":
return checkRank(w, req);
case "create_profile_req":
return createProfile(w, req);
@@ -110,6 +107,9 @@ export async function dispatch(w: World, req: Request): Promise<Response> {
case "save_stocker_req":
return saveStocker(w, req);
case "save_time_attack_req":
return saveTimeAttack(w, req);
case "unlock_profile_req":
return unlockProfile(w, req);
+2
View File
@@ -17,6 +17,7 @@ export async function loadProfile(
const missions = await w.missions().load(profile.id);
const car = await w.car().load(profile.id);
const story = await w.story().load(profile.id);
const timeAttack = await w.timeAttack().load(profile.id);
const unlocks = await w.unlocks().load(profile.id);
return {
@@ -33,6 +34,7 @@ export async function loadProfile(
titles,
coursePlays,
missions,
timeAttack,
car,
story,
unlocks,
+29
View File
@@ -0,0 +1,29 @@
import { SaveTimeAttackRequest } from "../request/saveTimeAttack";
import { SaveTimeAttackResponse } from "../response/saveTimeAttack";
import { World } from "../world";
export async function saveTimeAttack(
w: World,
req: SaveTimeAttackRequest
): Promise<SaveTimeAttackResponse> {
const state = await w.timeAttack().load(req.profileId);
const existing = state.courses.find(
course => course.courseId === req.payload.courseId
);
if (existing === undefined || existing.totalMsec > req.payload.totalMsec) {
const newCourses = state.courses.filter(
course => course.courseId !== req.payload.courseId
);
newCourses.push(req.payload);
const newState = { courses: newCourses };
await w.timeAttack().save(req.profileId, newState);
}
return {
type: "save_time_attack_res",
};
}
+1
View File
@@ -6,4 +6,5 @@ export { Profile } from "./profile";
export { Settings } from "./settings";
export { Story } from "./story";
export { Team } from "./team";
export { TimeAttackState, TimeAttackCourse } from "./timeAttack";
export { Unlocks } from "./unlocks";
+11
View File
@@ -0,0 +1,11 @@
export interface TimeAttackCourse {
courseId: number;
timestamp: string; // hack
flags: number;
totalMsec: number;
stageMsec: number[];
}
export interface TimeAttackState {
courses: TimeAttackCourse[];
}
-20
View File
@@ -1,20 +0,0 @@
export interface CheckRankRequest {
type: "check_rank_req";
field_0002: number; // u16, always 1
field_0004: number; // u32
field_0008: number; // u32
field_000C: number; // u16
field_0012: number; // u8
field_0015: number; // u8
field_0018: number; // u32
field_0024: number; // u32
field_0028: number; // u32
field_002C: number; // u32
field_0054: number; // u8
field_0058: number; // u32
field_005C: number; // u8 bit map
field_005D: number; // u8
field_005E: number; // u16
field_0060: number; // u16
field_0062: number; // u8
}
+2 -2
View File
@@ -1,4 +1,3 @@
import { CheckRankRequest } from "./checkRank";
import { CreateProfileRequest } from "./createProfile";
import { CreateTeamRequest } from "./createTeam";
import { DiscoverProfileRequest } from "./discoverProfile";
@@ -22,6 +21,7 @@ import { SaveGarageRequest } from "./saveGarage";
import { SaveProfileRequest } from "./saveProfile";
import { SaveSettingsRequest } from "./saveSettings";
import { SaveStockerRequest } from "./saveStocker";
import { SaveTimeAttackRequest } from "./saveTimeAttack";
import { SaveTopicRequest } from "./saveTopic";
import { UnlockProfileRequest } from "./unlockProfile";
import { UpdateProvisionalStoreRankRequest } from "./updateProvisionalStoreRank";
@@ -32,7 +32,6 @@ import { UpdateUiReportRequest } from "./updateUiReport";
import { UpdateUserLogRequest } from "./updateUserLog";
export type Request =
| CheckRankRequest
| CreateProfileRequest
| CreateTeamRequest
| DiscoverProfileRequest
@@ -56,6 +55,7 @@ export type Request =
| SaveProfileRequest
| SaveSettingsRequest
| SaveStockerRequest
| SaveTimeAttackRequest
| SaveTopicRequest
| UnlockProfileRequest
| UpdateProvisionalStoreRankRequest
+19
View File
@@ -0,0 +1,19 @@
import { Id } from "../model/base";
import { Profile } from "../model/profile";
import { TimeAttackCourse } from "../model/timeAttack";
export interface SaveTimeAttackRequest {
type: "save_time_attack_req";
profileId: Id<Profile>; // u32
dayNight: number;
payload: TimeAttackCourse;
field_0002: number; // u16, always 1
field_0008: number; // u32
field_000C: number; // u16, car type?
field_0012: number; // u8
field_0015: number; // u8
field_005D: number; // u8
field_005E: number; // u16
field_0060: number; // u16
field_0062: number; // u8
}
-3
View File
@@ -1,3 +0,0 @@
export interface CheckRankResponse {
type: "check_rank_res";
}
+5 -5
View File
@@ -1,4 +1,3 @@
import { CheckRankResponse } from "./checkRank";
import { CreateTeamResponse } from "./createTeam";
import { DiscoverProfileResponse } from "./discoverProfile";
import { GenericResponse } from "./generic";
@@ -16,15 +15,15 @@ import { LoadTeamResponse } from "./loadTeam";
import { LoadTeamRankingResponse } from "./loadTeamRanking";
import { LoadTopTenResponse } from "./loadTopTen";
import { LockProfileResponse } from "./lockProfile";
import { SaveExpeditionResponse } from "./saveExpedition";
import { SaveGarageResponse } from "./saveGarage";
import { SaveTimeAttackResponse } from "./saveTimeAttack";
import { SaveTopicResponse } from "./saveTopic";
import { UpdateProvisionalStoreRankResponse } from "./updateProvisionalStoreRank";
import { UpdateStoryClearNumResponse } from "./updateStoryClearNum";
import { UnlockProfileResponse } from "./unlockProfile";
import { SaveExpeditionResponse } from "./saveExpedition";
import { SaveGarageResponse } from "./saveGarage";
import { SaveTopicResponse } from "./saveTopic";
export type Response =
| CheckRankResponse
| CreateTeamResponse
| DiscoverProfileResponse
| GenericResponse
@@ -47,4 +46,5 @@ export type Response =
| UpdateStoryClearNumResponse
| SaveExpeditionResponse
| SaveGarageResponse
| SaveTimeAttackResponse
| SaveTopicResponse;
+2
View File
@@ -5,6 +5,7 @@ import { MissionState } from "../model/mission";
import { Profile } from "../model/profile";
import { Settings } from "../model/settings";
import { Story } from "../model/story";
import { TimeAttackState } from "../model/timeAttack";
import { Unlocks } from "../model/unlocks";
export interface LoadProfileResponse2 {
@@ -21,6 +22,7 @@ export interface LoadProfileResponse2 {
titles: TitleCode[];
coursePlays: number[];
missions: MissionState;
timeAttack: TimeAttackState;
car: Car;
story: Story;
unlocks: Unlocks;
+3
View File
@@ -0,0 +1,3 @@
export interface SaveTimeAttackResponse {
type: "save_time_attack_res";
}
+3
View File
@@ -31,6 +31,9 @@ export interface World {
story(): Repository<Model.Story, Model.Profile>;
// this is godawful do a real repo layer already jfc
timeAttack(): Repository<Model.TimeAttackState, Model.Profile>;
titles(): Repository<Model.TitleCode[], Model.Profile>;
unlocks(): Repository<Model.Unlocks, Model.Profile>;
+7
View File
@@ -64,6 +64,13 @@ class WorldImpl implements World {
);
}
timeAttack() {
return new SingletonJsonRepo<Model.TimeAttackState, Model.Profile>(
this._root,
"timeAttack"
);
}
titles() {
return new SingletonTextRepo<Model.TitleCode[], Model.Profile>(
this._root,