diff --git a/src/idz/decoder/loadTopTen.ts b/src/idz/decoder/loadTopTen.ts index 0851e0e..53c1be8 100644 --- a/src/idz/decoder/loadTopTen.ts +++ b/src/idz/decoder/loadTopTen.ts @@ -1,11 +1,33 @@ import { RequestCode } from "./_defs"; +import { Id } from "../model/base"; +import { Profile } from "../model/profile"; +import { Team } from "../model/team"; import { LoadTopTenRequest } from "../request/loadTopTen"; loadTopTen.msgCode = 0x00b5 as RequestCode; loadTopTen.msgLen = 0x00e0; export function loadTopTen(buf: Buffer): LoadTopTenRequest { + const selectors = new Array(); + + for (let i = 0; i < 32; i++) { + selectors.push({ + field_4: buf.readUInt16LE(0x0004 + 2 * i), + field_44: buf.readUInt32LE(0x0044 + 4 * i), + }); + } + + const profileId = buf.readUInt32LE(0x00c8); + const teamId = buf.readUInt32LE(0x00cc); + return { type: "load_top_ten_req", + field_2: buf.readUInt16LE(0x0002), // Bitmask selector + selectors, + field_C4: buf.readUInt8(0x00c4), // Boolean, true if profile ID is set + field_C5: buf.readUInt8(0x00c5), // Always zero + field_C6: buf.readUInt16LE(0x00c6), + profileId: profileId !== 0 ? (profileId as Id) : undefined, + teamId: teamId !== 0xffffffff ? (teamId as Id) : undefined, }; } diff --git a/src/idz/encoder/loadTopTen.ts b/src/idz/encoder/loadTopTen.ts index a13b35e..7f530f9 100644 --- a/src/idz/encoder/loadTopTen.ts +++ b/src/idz/encoder/loadTopTen.ts @@ -1,10 +1,71 @@ +import iconv = require("iconv-lite"); + import { LoadTopTenResponse } from "../response/loadTopTen"; export function loadTopTen(res: LoadTopTenResponse): Buffer { const buf = Buffer.alloc(0x1720); buf.writeUInt16LE(0x00b6, 0x0000); - // in awe of the size of this lad too + buf.writeUInt16LE(res.totalSelected, 0x0002); + + for (let i = 0; i < 3; i++) { + if (i >= res.courses.length) { + break; + } + + const course = res.courses[i]; + const outerOff = 0x0004 + 0x794 * i; + + buf.writeUInt16LE(course.courseId, outerOff + 0x0000); + buf.writeUInt16LE(course.field_02, outerOff + 0x0002); // Bitmask-y? + buf.writeUInt32LE(course.wrStageMsec[0], outerOff + 0x0004); + buf.writeUInt32LE(course.wrStageMsec[1], outerOff + 0x0008); + buf.writeUInt32LE(course.wrStageMsec[2], outerOff + 0x000c); + + for (let j = 0; j < 10; j++) { + if (j >= course.rows.length) { + break; + } + + const row = course.rows[i]; + const innerOff = outerOff + 0x0010 + 0xc0 * j; + + buf.writeUInt32LE(row.field_00, innerOff + 0x0000); + buf.writeUInt32LE(row.totalMsec, innerOff + 0x0004); + buf.writeUInt32LE( + (row.timestamp.getTime() / 1000) | 0, + innerOff + 0x0008 + ); + buf.writeUInt16LE(row.car, innerOff + 0x000c); // CAR + buf.writeUInt8(row.field_0E ? 1 : 0, innerOff + 0x000e); // Boolean + buf.writeUInt8(row.field_0F ? 1 : 0, innerOff + 0x000f); // Boolean + buf.writeUInt8(row.field_10, innerOff + 0x0010); + iconv.encode(row.driverName, "shift_jis").copy(buf, innerOff + 0x0014); + iconv.encode(row.teamName, "shift_jis").copy(buf, innerOff + 0x0028); + iconv.encode(row.shopName, "shift_jis").copy(buf, innerOff + 0x0048); + buf.writeUInt32LE(row.field_74, innerOff + 0x0074); + buf.writeUInt16LE(row.field_78, innerOff + 0x0078); + buf.writeUInt8(row.field_7C, innerOff + 0x007c); + buf.writeUInt8(row.field_7D, innerOff + 0x007d); + // 66 bytes of fucking nothing what + } + } + + for (let i = 0; i < 3; i++) { + const offset = 0x16c0 + 0x1c * i; + + if (i < res.trailers.length) { + const trailer = res.trailers[i]; + + buf.writeUInt16LE(trailer.yearMonth, offset + 0x00); + buf.writeUInt8(trailer.courseId, offset + 0x02); + buf.writeUInt8(trailer.isNight ? 1 : 0, offset + 0x03); + buf.writeUInt32LE(trailer.totalMsec, offset + 0x04); + iconv.encode(trailer.name, "shift_jis").copy(buf, offset + 0x08); + } else { + buf.writeUInt8(0xff, offset + 0x02); + } + } return buf; } diff --git a/src/idz/handler/loadTopTen.ts b/src/idz/handler/loadTopTen.ts index 1ce830e..6c8112f 100644 --- a/src/idz/handler/loadTopTen.ts +++ b/src/idz/handler/loadTopTen.ts @@ -8,5 +8,8 @@ export function loadTopTen( ): LoadTopTenResponse { return { type: "load_top_ten_res", + totalSelected: 0, + courses: [], + trailers: [], }; } diff --git a/src/idz/request/loadTopTen.ts b/src/idz/request/loadTopTen.ts index e1ef029..0af609f 100644 --- a/src/idz/request/loadTopTen.ts +++ b/src/idz/request/loadTopTen.ts @@ -1,3 +1,19 @@ +import { Id } from "../model/base"; +import { Profile } from "../model/profile"; +import { Team } from "../model/team"; + +export interface LoadTopTenRequestSelector { + field_4: number; + field_44: number; +} + export interface LoadTopTenRequest { type: "load_top_ten_req"; + field_2: number; + selectors: LoadTopTenRequestSelector[]; + field_C4: number; + field_C5: number; + field_C6: number; + profileId?: Id; + teamId?: Id; } diff --git a/src/idz/response/loadTopTen.ts b/src/idz/response/loadTopTen.ts index 4dd910f..8e01e45 100644 --- a/src/idz/response/loadTopTen.ts +++ b/src/idz/response/loadTopTen.ts @@ -1,3 +1,41 @@ +import { CarSelector } from "../model/car"; + +export interface LoadTopTenResponseRow { + field_00: number; + totalMsec: number; + timestamp: Date; + car: CarSelector; + field_0E: boolean; + field_0F: boolean; + field_10: number; + driverName: string; + teamName: string; + shopName: string; + field_74: number; + field_78: number; + field_7C: number; + field_7D: number; +} + +export interface LoadTopTenResponseCourse { + courseId: number; // Multiplied by 2! Includes day/night i guess + field_02: number; + wrStageMsec: number[]; // Sent OOB, for first-place only + rows: LoadTopTenResponseRow[]; +} + +export interface LoadTopTenResponseTrailer { + // Something team related... + yearMonth: number; + courseId: number; + isNight: boolean; + totalMsec: number; + name: string; +} + export interface LoadTopTenResponse { type: "load_top_ten_res"; + totalSelected: number; + courses: LoadTopTenResponseCourse[]; + trailers: LoadTopTenResponseTrailer[]; }