Save Time Attack records
This commit is contained in:
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
@@ -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),
|
||||
]);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export interface TimeAttackCourse {
|
||||
courseId: number;
|
||||
timestamp: string; // hack
|
||||
flags: number;
|
||||
totalMsec: number;
|
||||
stageMsec: number[];
|
||||
}
|
||||
|
||||
export interface TimeAttackState {
|
||||
courses: TimeAttackCourse[];
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export interface CheckRankResponse {
|
||||
type: "check_rank_res";
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface SaveTimeAttackResponse {
|
||||
type: "save_time_attack_res";
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user