diff --git a/src/idz/decoder/checkRank.ts b/src/idz/decoder/checkRank.ts deleted file mode 100644 index 9df7b54..0000000 --- a/src/idz/decoder/checkRank.ts +++ /dev/null @@ -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), - }; -} diff --git a/src/idz/decoder/index.ts b/src/idz/decoder/index.ts index 3323191..f606ef8 100644 --- a/src/idz/decoder/index.ts +++ b/src/idz/decoder/index.ts @@ -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, diff --git a/src/idz/decoder/saveTimeAttack.ts b/src/idz/decoder/saveTimeAttack.ts new file mode 100644 index 0000000..27574cd --- /dev/null +++ b/src/idz/decoder/saveTimeAttack.ts @@ -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, + 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), + }; +} diff --git a/src/idz/encoder/checkRank.ts b/src/idz/encoder/checkRank.ts deleted file mode 100644 index be72fbf..0000000 --- a/src/idz/encoder/checkRank.ts +++ /dev/null @@ -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; -} diff --git a/src/idz/encoder/index.ts b/src/idz/encoder/index.ts index 3a72eda..28ebccc 100644 --- a/src/idz/encoder/index.ts +++ b/src/idz/encoder/index.ts @@ -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); diff --git a/src/idz/encoder/loadProfile2.ts b/src/idz/encoder/loadProfile2.ts index 2e18626..5120ae1 100644 --- a/src/idz/encoder/loadProfile2.ts +++ b/src/idz/encoder/loadProfile2.ts @@ -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]; diff --git a/src/idz/encoder/saveTimeAttack.ts b/src/idz/encoder/saveTimeAttack.ts new file mode 100644 index 0000000..f64b91c --- /dev/null +++ b/src/idz/encoder/saveTimeAttack.ts @@ -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; +} diff --git a/src/idz/handler/checkRank.ts b/src/idz/handler/checkRank.ts deleted file mode 100644 index c878901..0000000 --- a/src/idz/handler/checkRank.ts +++ /dev/null @@ -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", - }; -} diff --git a/src/idz/handler/createProfile.ts b/src/idz/handler/createProfile.ts index 50f9d4c..973d368 100644 --- a/src/idz/handler/createProfile.ts +++ b/src/idz/handler/createProfile.ts @@ -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), ]); diff --git a/src/idz/handler/index.ts b/src/idz/handler/index.ts index 228bf65..48cedf0 100644 --- a/src/idz/handler/index.ts +++ b/src/idz/handler/index.ts @@ -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 { 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 { 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); diff --git a/src/idz/handler/loadProfile.ts b/src/idz/handler/loadProfile.ts index 3df0050..7717d8f 100644 --- a/src/idz/handler/loadProfile.ts +++ b/src/idz/handler/loadProfile.ts @@ -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, diff --git a/src/idz/handler/saveTimeAttack.ts b/src/idz/handler/saveTimeAttack.ts new file mode 100644 index 0000000..0227052 --- /dev/null +++ b/src/idz/handler/saveTimeAttack.ts @@ -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 { + 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", + }; +} diff --git a/src/idz/model/index.ts b/src/idz/model/index.ts index ba35777..007c696 100644 --- a/src/idz/model/index.ts +++ b/src/idz/model/index.ts @@ -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"; diff --git a/src/idz/model/timeAttack.ts b/src/idz/model/timeAttack.ts new file mode 100644 index 0000000..5cd0f0a --- /dev/null +++ b/src/idz/model/timeAttack.ts @@ -0,0 +1,11 @@ +export interface TimeAttackCourse { + courseId: number; + timestamp: string; // hack + flags: number; + totalMsec: number; + stageMsec: number[]; +} + +export interface TimeAttackState { + courses: TimeAttackCourse[]; +} diff --git a/src/idz/request/checkRank.ts b/src/idz/request/checkRank.ts deleted file mode 100644 index c486ce0..0000000 --- a/src/idz/request/checkRank.ts +++ /dev/null @@ -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 -} diff --git a/src/idz/request/index.ts b/src/idz/request/index.ts index cea92cb..ff7852b 100644 --- a/src/idz/request/index.ts +++ b/src/idz/request/index.ts @@ -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 diff --git a/src/idz/request/saveTimeAttack.ts b/src/idz/request/saveTimeAttack.ts new file mode 100644 index 0000000..4ce45d8 --- /dev/null +++ b/src/idz/request/saveTimeAttack.ts @@ -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; // 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 +} diff --git a/src/idz/response/checkRank.ts b/src/idz/response/checkRank.ts deleted file mode 100644 index 0ae48a3..0000000 --- a/src/idz/response/checkRank.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface CheckRankResponse { - type: "check_rank_res"; -} diff --git a/src/idz/response/index.ts b/src/idz/response/index.ts index a7e8c10..75b94b4 100644 --- a/src/idz/response/index.ts +++ b/src/idz/response/index.ts @@ -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; diff --git a/src/idz/response/loadProfile2.ts b/src/idz/response/loadProfile2.ts index 0b0d90b..5365dfd 100644 --- a/src/idz/response/loadProfile2.ts +++ b/src/idz/response/loadProfile2.ts @@ -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; diff --git a/src/idz/response/saveTimeAttack.ts b/src/idz/response/saveTimeAttack.ts new file mode 100644 index 0000000..0cdb5e0 --- /dev/null +++ b/src/idz/response/saveTimeAttack.ts @@ -0,0 +1,3 @@ +export interface SaveTimeAttackResponse { + type: "save_time_attack_res"; +} diff --git a/src/idz/world/base.ts b/src/idz/world/base.ts index 7b1178e..b6eb81e 100644 --- a/src/idz/world/base.ts +++ b/src/idz/world/base.ts @@ -31,6 +31,9 @@ export interface World { story(): Repository; + // this is godawful do a real repo layer already jfc + timeAttack(): Repository; + titles(): Repository; unlocks(): Repository; diff --git a/src/idz/world/index.ts b/src/idz/world/index.ts index f87356d..7ebe539 100644 --- a/src/idz/world/index.ts +++ b/src/idz/world/index.ts @@ -64,6 +64,13 @@ class WorldImpl implements World { ); } + timeAttack() { + return new SingletonJsonRepo( + this._root, + "timeAttack" + ); + } + titles() { return new SingletonTextRepo( this._root,