65 Commits
Author SHA1 Message Date
Emi Midnight 87661cf871 check for both null AND undefined in stream writer (#44)
This fixes IDZ support when minime is run using a newer LTS version of
node js.
(Streams use null for the error argument now for write and end
callbacks)
2024-03-19 11:59:08 +01:00
Tau d6d98aa561 v016 2021-02-04 17:03:11 -05:00
Tau 2c78e6646a Fix trailing comma in SQL 2021-02-04 17:02:23 -05:00
Tau 0f8010ae8d v015 2021-02-04 16:47:21 -05:00
Tau cb5db5e55f idz: Drop idz_team_auto_uq constraint 2021-02-04 16:41:42 -05:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 a99503a86d idz: Fix team message encoding 2021-02-04 16:32:03 -05:00
Tau 37660a382a v014 2021-02-03 18:51:28 -05:00
Tau e43faeff10 idz: Fix SqlTeamRepository 2021-02-03 23:43:46 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 2189132753 idz: Fix logic in SqlStoryRepository 2021-02-03 23:43:45 +00:00
Tau 85aad3377e idz: Fix SqlStampsRepository 2021-02-03 23:43:45 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 2f5a63858f idz: SqlSettingsRepository: Persist driving_style 2021-02-03 23:43:45 +00:00
Tau dbffb15779 idz: Fix SqlProfileRepository 2021-02-03 23:43:45 +00:00
Tau 1f544ddb7e idz: Fix SqlMyCharaRepository 2021-02-03 23:43:45 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 7f47315130 idz: Save weekly missions progress 2021-02-03 23:43:45 +00:00
Tau afe9f27c25 idz: Fix SqlWeeklyMissionsRepository 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 f8ca0bdcd3 idz: Fix remaining encoder mappings 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 98caa9248b idz: Split/fix loadServerList encoder 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 0236d44466 idz: Split/fix loadRewardTable encoder 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 4c0e933ea4 idz: Split/fix loadGhost encoder 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 8b284107b1 idz: Split/fix "generic" response encoding 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 2c31bd0c47 idz: Fix _team encoders 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 7b329bc37c idz: Fix unknownA_2 message length rounding 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 0937c0fadf idz: Fix saveGarage processing 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 ea92e4e981 idz: Fix loadProfile codecs 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 1cfd577f56 idz: Fix loadGeneralReward codecs 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 b7187fe0ed idz: Fix updateStoryClearNum codecs 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 60c089a810 idz: Fix saveTimeAttack codecs 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 377096a077 idz: Fix loadTopTen 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 87de79ef12 idz: Fix loadTeamRanking mapping 2021-02-03 23:43:43 +00:00
Tau f1386f8e3d idz: Split/fix loadConfig messages 2021-02-03 23:43:42 +00:00
Tau 6d581c114d idz: Split/fix load2on2 messages 2021-02-03 23:43:42 +00:00
Tau 9570349565 idz: Plumb clientHello into request dispatcher 2021-02-03 23:43:42 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 206d12742a idz: Ignore ServerBox traffic 2021-02-03 23:43:42 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 f2434cef7e idz: Add idz2 encoders 2021-02-03 23:43:42 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 2474879c89 idz: Add idz2 decoders 2021-02-03 23:43:42 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 4c36e0abf0 idz: Add LoadStockerResponse.myChara field 2021-02-03 23:43:42 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 ccd5fd72c8 idz: Add field "c" to story cell tuple 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 46499a5425 idz: Add UpdateExpedition command 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 8562da15b6 idz: Add SaveStockerRequest.myChara 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 8195e9f937 idz: Add stamps to SaveStockerRequest 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 9f57ff1342 idz: Add idz_my_chara table 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 a7b7a01685 idz: Add idz_weekly_missions table 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 8ada783f7c idz: Add stamps tables 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 f79c4a4370 idz: Add idz_settings.driving_style column 2021-02-03 23:43:41 +00:00
Tau f3be39823a idz: Explicitly split team msg encoders 2021-02-03 23:43:40 +00:00
Tau 03d0e87b55 idz: Push story geometry down into codecs
Sparsify the data model returned from the repository layer. Different
versions of the game can have drastically different dimensions for the
story data grid, so handling those dimensions is best done in the
encoders themselves.
2021-02-03 23:43:40 +00:00
Tau efcc1caa76 idz: Add explicit CreateProfileResponse 2021-02-03 23:43:40 +00:00
Tau 2a546c0d9d idz: Generalize msg00AD
The purpose of this request is unknown, but its message code
changed in idz2.
2021-02-03 23:43:40 +00:00
Tau 63627eaf78 idz: Combine loadConfig requests 2021-02-03 23:43:40 +00:00
Tau ed7c461fe1 idz: Dispatch on declared protocol version 2021-02-03 23:43:39 +00:00
Tau 75ac6e03f2 idz: Support multiple major versions
Doesn't actually add support for any additional versions, but it
does lay the groundwork.
2021-02-03 23:43:39 +00:00
Tau 2f2dc82b5a idz: Add type defs for common net comms context 2021-02-03 23:43:38 +00:00
Tau 198256e6ff idz: Add implicit version field to requests 2021-02-03 23:43:38 +00:00
Tau eb276128c2 sql: Add "maintenance transaction" system
SQLite's ALTER TABLE functionality leaves a lot to be desired, so
in practice the way you alter SQLite schema is to create a new
table, migrate the data into it, then drop the old table. However,
this is potentially complicated by the presence of foreign key
constraints.

In order to resolve the foreign key constraint issue we add a
`maintenance()` method to the `DataSource` abstraction layer. This
operates similarly to the existing `transaction()` method, but the
SQLite implementation of this method defers enforcement of foreign
keys until the transaction is about to commit.
2021-02-03 23:43:38 +00:00
Tau 6090a60467 sql: Drive-by method documentation 2021-02-03 23:43:38 +00:00
Tau 2099622b0f sql: Throw conventionally... 2021-02-03 23:43:38 +00:00
Tau 9494f3026b checkdb: Use single transaction 2021-02-03 23:43:38 +00:00
Tau f7ceb6bcda idz: Prettier compliance 2021-02-03 23:43:38 +00:00
Tau 04dac7279e idz: Remove some dead code 2021-02-03 23:43:37 +00:00
Tau 8c164b6130 idz: Use string I/O helpers 2021-02-03 23:43:37 +00:00
Tau 27b229e1c2 idz: Remove iffy redundant NUL termination op 2021-02-03 23:43:37 +00:00
Tau c877d9d214 idz: Fix apparent buffer stride bug 2021-02-03 23:43:37 +00:00
Tau 636789b8bf idz: Delete dead imports 2021-02-03 23:43:37 +00:00
Tau 48aeb1c9d2 types: Declare ON CONFLICT ... DO NOTHING clause 2021-02-03 23:43:36 +00:00
Tau 5f09c00eb4 Add explicit vscode formatter conf 2021-02-03 23:43:36 +00:00
187 changed files with 3271 additions and 884 deletions
+4 -1
View File
@@ -1,5 +1,8 @@
{
"editor.formatOnSave": true,
"editor.rulers": [79],
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
+12
View File
@@ -1,5 +1,17 @@
# CHANGELOG
## v016
- IDZ: Fix SQL syntax error (Tau)
## v015
- IDZ: Team fixes (BemaniWitch)
## v014
- IDZ: Add support for Initial D v2.1 (BemaniWitch)
## v013
- IDZ: Miscellaneous refactoring (Tau)
+52 -5
View File
@@ -7,6 +7,10 @@ create table "idz_profile" (
"player_id" integer not null
references "aime_player"("id")
on delete cascade,
-- Major version of Initial D Zero, either 1 or 2.
-- The two major versions are incompatible with each other and do not
-- permit the player to carry progress over from one to the other.
"version" integer not null,
-- TODO shop_id
"name" text not null,
"lv" integer not null,
@@ -16,7 +20,7 @@ create table "idz_profile" (
"mileage" integer not null,
"register_time" timestamp not null,
"access_time" timestamp not null,
constraint "idz_profile_player_uq" unique ("player_id")
constraint "idz_profile_player_uq" unique ("player_id", "version")
);
create table "idz_chara" (
@@ -100,7 +104,27 @@ create table "idz_settings" (
"pack" integer not null,
"aura" integer not null,
"paper_cup" integer not null, -- Not a boolean, oddly enough
"gauges" integer not null
"gauges" integer not null,
"driving_style" integer not null
);
create table "idz_stamp_selections" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"stamp_01" integer not null,
"stamp_02" integer not null,
"stamp_03" integer not null,
"stamp_04" integer not null
);
create table "idz_stamp_unlock" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"stamp_no",
constraint "idz_stamp_unlock_uq" unique ("profile_id", "stamp_no")
);
create table "idz_story_state" (
@@ -120,6 +144,7 @@ create table "idz_story_cell_state" (
"col_no" integer not null,
"a" integer not null,
"b" integer not null,
"c" integer not null,
constraint "idz_story_cell_state_uq" unique (
"profile_id",
"row_no",
@@ -202,12 +227,13 @@ create table "idz_unlocks" (
create table "idz_team" (
"id" integer primary key not null,
"version" integer not null,
"ext_id" integer not null,
"name" text not null,
"name_bg" integer not null,
"name_fx" integer not null,
"register_time" timestamp not null,
constraint "idz_team_uq" unique ("ext_id")
constraint "idz_team_uq" unique ("version", "ext_id")
);
create table "idz_team_auto" (
@@ -215,8 +241,7 @@ create table "idz_team_auto" (
references "idz_team"("id")
on delete cascade,
"serial_no" integer not null,
"name_idx" integer not null,
constraint "idz_team_auto_uq" unique ("serial_no", "name_idx")
"name_idx" integer not null
);
create table "idz_team_member" (
@@ -240,3 +265,25 @@ create table "idz_team_reservation" (
"join_time" timestamp not null,
"leader" boolean not null
);
create table "idz_weekly_missions" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"weekly_reset" timestamp not null,
"mission_left" integer not null,
"progress_left" integer not null,
"params_left" integer not null,
"mission_right" integer not null,
"progress_right" integer not null,
"params_right" integer not null
);
create table "idz_my_chara" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"my_chara_no" integer not null,
constraint "idz_my_chara_uq" unique ("profile_id", "my_chara_no")
);
@@ -0,0 +1,77 @@
create table "new_idz_profile" (
"id" integer primary key not null,
"player_id" integer not null
references "aime_player"("id")
on delete cascade,
"version" integer not null,
"name" text not null,
"lv" integer not null,
"exp" integer not null,
"fame" integer not null,
"dpoint" integer not null,
"mileage" integer not null,
"register_time" timestamp not null,
"access_time" timestamp not null,
constraint "idz_profile_player_uq" unique ("player_id", "version")
);
create table "new_idz_team" (
"id" integer primary key not null,
"version" integer not null,
"ext_id" integer not null,
"name" text not null,
"name_bg" integer not null,
"name_fx" integer not null,
"register_time" timestamp not null,
constraint "idz_team_uq" unique ("version", "ext_id")
);
insert into "new_idz_profile" (
"id",
"player_id",
"version",
"name",
"lv",
"exp",
"fame",
"dpoint",
"mileage",
"register_time",
"access_time"
) select
x."id",
x."player_id",
1,
x."name",
x."lv",
x."exp",
x."fame",
x."dpoint",
x."mileage",
x."register_time",
x."access_time"
from "idz_profile" as "x";
insert into "new_idz_team" (
"id",
"version",
"ext_id",
"name",
"name_bg",
"name_fx",
"register_time"
) select
x."id",
1,
x."ext_id",
x."name",
x."name_bg",
x."name_fx",
x."register_time"
from "idz_team" as "x";
drop table "idz_profile";
drop table "idz_team";
alter table "new_idz_profile" rename to "idz_profile";
alter table "new_idz_team" rename to "idz_team";
+111
View File
@@ -0,0 +1,111 @@
create table "idz_my_chara" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"my_chara_no" integer not null,
constraint "idz_my_chara_uq" unique ("profile_id", "my_chara_no")
);
create table "new_idz_settings" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"music" integer not null,
"pack" integer not null,
"aura" integer not null,
"paper_cup" integer not null, -- Not a boolean, oddly enough
"gauges" integer not null,
"driving_style" integer not null
);
create table "idz_stamp_selections" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"stamp_01" integer not null,
"stamp_02" integer not null,
"stamp_03" integer not null,
"stamp_04" integer not null
);
create table "idz_stamp_unlock" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"stamp_no",
constraint "idz_stamp_unlock_uq" unique ("profile_id", "stamp_no")
);
create table "new_idz_story_cell_state" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"row_no" integer not null,
"col_no" integer not null,
"a" integer not null,
"b" integer not null,
"c" integer not null,
constraint "idz_story_cell_state_uq" unique (
"profile_id",
"row_no",
"col_no"
)
);
create table "idz_weekly_missions" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"weekly_reset" timestamp not null,
"mission_left" integer not null,
"progress_left" integer not null,
"params_left" integer not null,
"mission_right" integer not null,
"progress_right" integer not null,
"params_right" integer not null
);
insert into "new_idz_settings" (
"id",
"music",
"pack",
"aura",
"paper_cup",
"gauges",
"driving_style"
) select
x."id",
x."music",
x."pack",
x."aura",
x."paper_cup",
x."gauges",
0
from "idz_settings" as x;
insert into "new_idz_story_cell_state" (
"id",
"profile_id",
"row_no",
"col_no",
"a",
"b",
"c"
) select
x."id",
x."profile_id",
x."row_no",
x."col_no",
x."a",
x."b",
0
from "idz_story_cell_state" as x;
drop table "idz_settings";
drop table "idz_story_cell_state";
alter table "new_idz_settings" rename to "idz_settings";
alter table "new_idz_story_cell_state" rename to "idz_story_cell_state";
@@ -0,0 +1,13 @@
create table "new_idz_team_auto" (
"id" integer primary key not null
references "idz_team"("id")
on delete cascade,
"serial_no" integer not null,
"name_idx" integer not null
);
insert into "new_idz_team_auto"
select "id", "serial_no", "name_idx" from "idz_team_auto";
drop table "idz_team_auto";
alter table "new_idz_team_auto" rename to "idz_team_auto";
+20 -18
View File
@@ -104,30 +104,32 @@ async function migratedb(
}
export default async function checkdb(db: DataSource): Promise<void> {
const stmt = sql.select("schemaver").from("meta");
let maybe: number | undefined;
let schemaver: number | undefined;
try {
const row = await db.transaction(txn => txn.fetchRow(stmt));
await db.maintenance(async txn => {
const stmt = sql.select("schemaver").from("meta");
if (row !== undefined) {
maybe = parseInt(row.schemaver!);
try {
const row = await txn.fetchRow(stmt);
if (row !== undefined) {
schemaver = parseInt(row.schemaver!);
}
} catch (e) {
return await initdb(txn);
}
} catch (e) {
return db.transaction(initdb);
}
if (maybe === undefined) {
throw new Error(
"Database corrupted: `meta` table singleton row is missing"
);
}
if (schemaver === undefined) {
throw new Error(
"Database corrupted: `meta` table singleton row is missing"
);
}
const schemaver = maybe;
const newver = await db.transaction(txn => migratedb(txn, schemaver));
schemaver = await migratedb(txn, schemaver);
});
if (newver !== undefined) {
debug("Upgraded database to version %s", newver);
if (schemaver !== undefined) {
debug("Upgraded database to version %s", schemaver);
await db.vacuum();
+1
View File
@@ -1,4 +1,5 @@
import setup from "./setup";
export { BLOCK_SIZE } from "./aes";
export { ClientHello } from "./setup";
export default setup;
+20 -3
View File
@@ -2,7 +2,7 @@ import { Socket } from "net";
import AesEcbStream from "./aes";
import { readBigInt, modPow, writeBigInt } from "./bigint";
import IoByteStream from "../../util/stream";
import IoByteStream, { ByteStream } from "../../util/stream";
interface RsaKey {
N: bigint;
@@ -11,6 +11,17 @@ interface RsaKey {
hashN: number;
}
export interface ClientHello {
pcbId: string;
protocol: string;
model: string;
}
export interface IdzConnection {
aesStream: ByteStream;
clientHello: ClientHello;
}
// Proof-of-concept, so we only ever use one of the ten RSA key pairs
const rsaKey = {
N: 4922323266120814292574970172377860734034664704992758249880018618131907367614177800329506877981986877921220485681998287752778495334541127048495486311792061n,
@@ -22,7 +33,7 @@ const rsaKey = {
// Proof-of-concept, so we only use one fixed session key
const aesKey = Buffer.from("ffddeeccbbaa99887766554433221100", "hex");
function writeServerHello(aesKey: Buffer, rsaKey: RsaKey) {
function writeServerHello(aesKey: Buffer, rsaKey: RsaKey): Buffer {
const M = readBigInt(aesKey);
const keyEnc = modPow(M, rsaKey.e, rsaKey.N);
const result = Buffer.alloc(0x48);
@@ -37,6 +48,12 @@ function writeServerHello(aesKey: Buffer, rsaKey: RsaKey) {
function readClientHello(buf: Buffer) {
const magic = buf.readUInt32LE(0x00);
if (magic === 0xFE78571D) {
throw new Error(
"Server Box data, ignore."
);
}
if (magic !== 0x01020304) {
throw new Error(
"Invalid magic number, cryptographic processing probably incorrect."
@@ -50,7 +67,7 @@ function readClientHello(buf: Buffer) {
};
}
export default async function setup(socket: Socket) {
export default async function setup(socket: Socket): Promise<IdzConnection> {
const tcpStream = new IoByteStream(socket);
const serverHello = writeServerHello(aesKey, rsaKey);
+10 -3
View File
@@ -1,8 +1,15 @@
import { CheckTeamNameRequest } from "../request/checkTeamName";
checkTeamName.msgCode = 0x00a2;
checkTeamName.msgLen = 0x0040;
checkTeamName1.msgCode = 0x00a2;
checkTeamName1.msgLen = 0x0040;
export function checkTeamName(buf: Buffer): CheckTeamNameRequest {
export function checkTeamName1(buf: Buffer): CheckTeamNameRequest {
return { type: "check_team_name_req" };
}
checkTeamName2.msgCode = 0x0097;
checkTeamName2.msgLen = 0x0040;
export function checkTeamName2(buf: Buffer): CheckTeamNameRequest {
return { type: "check_team_name_req" };
}
+17 -3
View File
@@ -1,13 +1,27 @@
import { CreateAutoTeamRequest } from "../request/createAutoTeam";
import { AimeId } from "../../../model";
createAutoTeam.msgCode = 0x007b;
createAutoTeam.msgLen = 0x0010;
createAutoTeam1.msgCode = 0x007b;
createAutoTeam1.msgLen = 0x0010;
export function createAutoTeam(buf: Buffer): CreateAutoTeamRequest {
export function createAutoTeam1(buf: Buffer): CreateAutoTeamRequest {
return {
type: "create_auto_team_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 1,
field_0008: buf.readUInt32LE(0x0008),
field_000C: buf.readUInt8(0x000c),
};
}
createAutoTeam2.msgCode = 0x0077;
createAutoTeam2.msgLen = 0x0010;
export function createAutoTeam2(buf: Buffer): CreateAutoTeamRequest {
return {
type: "create_auto_team_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 2,
field_0008: buf.readUInt32LE(0x0008),
field_000C: buf.readUInt8(0x000c),
};
+23 -10
View File
@@ -1,24 +1,37 @@
import iconv from "iconv-lite";
import { car } from "./_car";
import { chara } from "./_chara";
import { CreateProfileRequest } from "../request/createProfile";
import { AimeId } from "../../../model";
import { readAsciiStr, readSjisStr } from "../../util/bin";
createProfile.msgCode = 0x0066;
createProfile.msgLen = 0x00c0;
createProfile1.msgCode = 0x0066;
createProfile1.msgLen = 0x00c0;
export function createProfile(buf: Buffer): CreateProfileRequest {
export function createProfile1(buf: Buffer): CreateProfileRequest {
return {
type: "create_profile_req",
aimeId: buf.readInt32LE(0x0004) as AimeId,
luid: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
name: iconv.decode(
buf.slice(0x001e, buf.indexOf("\0", 0x001e)),
"shift_jis"
),
version: 1,
luid: readAsciiStr(buf, 0x0008, 0x001e),
name: readSjisStr(buf, 0x001e, 0x0034),
field_0034: buf.readUInt32LE(0x0034),
car: car(buf.slice(0x0040, 0x00a0)),
chara: chara(buf.slice(0x00a0, 0x00b4)),
};
}
createProfile2.msgCode = 0x0064;
createProfile2.msgLen = 0x00c0;
export function createProfile2(buf: Buffer): CreateProfileRequest {
return {
type: "create_profile_req",
aimeId: buf.readInt32LE(0x0004) as AimeId,
version: 2,
luid: readAsciiStr(buf, 0x0008, 0x001e),
name: readSjisStr(buf, 0x001e, 0x0034),
field_0034: buf.readUInt32LE(0x0034),
car: car(buf.slice(0x0040, 0x00a0)),
chara: chara(buf.slice(0x00a8, 0x00bc)),
};
}
+25 -10
View File
@@ -1,24 +1,39 @@
import iconv from "iconv-lite";
import { CreateTeamRequest } from "../request/createTeam";
import { AimeId } from "../../../model";
import { readAsciiStr, readSjisStr } from "../../util/bin";
createTeam.msgCode = 0x0071;
createTeam.msgLen = 0x0050;
createTeam1.msgCode = 0x0071;
createTeam1.msgLen = 0x0050;
export function createTeam(buf: Buffer): CreateTeamRequest {
export function createTeam1(buf: Buffer): CreateTeamRequest {
return {
type: "create_team_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
teamName: iconv.decode(
buf.slice(0x0008, buf.indexOf("\0", 0x0008)),
"shift_jis"
),
version: 1,
teamName: readSjisStr(buf, 0x0008, 0x0028),
field_0028: buf.readUInt16LE(0x0028),
field_002C: buf.readUInt32LE(0x002c),
nameBg: buf.readUInt8(0x0030),
field_0032: buf.readUInt16LE(0x0032),
prevTeamId: buf.readUInt32LE(0x0034),
pcbId: buf.slice(0x0038, buf.indexOf("\0", 0x0038)).toString("ascii"),
pcbId: readAsciiStr(buf, 0x0038, 0x0050),
};
}
createTeam2.msgCode = 0x006d;
createTeam2.msgLen = 0x0050;
export function createTeam2(buf: Buffer): CreateTeamRequest {
return {
type: "create_team_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 2,
teamName: readSjisStr(buf, 0x0008, 0x0028),
field_0028: buf.readUInt16LE(0x0028),
field_002C: buf.readUInt32LE(0x002c),
nameBg: buf.readUInt8(0x0030),
field_0032: buf.readUInt16LE(0x0032),
prevTeamId: buf.readUInt32LE(0x0034),
pcbId: readAsciiStr(buf, 0x0038, 0x0050),
};
}
+15 -3
View File
@@ -1,12 +1,24 @@
import { DiscoverProfileRequest } from "../request/discoverProfile";
import { AimeId } from "../../../model";
discoverProfile.msgCode = 0x006b;
discoverProfile.msgLen = 0x0010;
discoverProfile1.msgCode = 0x006b;
discoverProfile1.msgLen = 0x0010;
export function discoverProfile(buf: Buffer): DiscoverProfileRequest {
export function discoverProfile1(buf: Buffer): DiscoverProfileRequest {
return {
type: "discover_profile_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 1,
};
}
discoverProfile2.msgCode = 0x0067;
discoverProfile2.msgLen = 0x0010;
export function discoverProfile2(buf: Buffer): DiscoverProfileRequest {
return {
type: "discover_profile_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 2,
};
}
+236 -110
View File
@@ -1,55 +1,81 @@
import logger from "debug";
import { checkTeamName } from "./checkTeamName";
import { createProfile } from "./createProfile";
import { createTeam } from "./createTeam";
import { createAutoTeam } from "./createAutoTeam";
import { discoverProfile } from "./discoverProfile";
import { load2on2_v1, load2on2_v2 } from "./load2on2";
import { loadConfig } from "./loadConfig";
import { loadConfig2 } from "./loadConfig2";
import { loadEventInfo } from "./loadEventInfo";
import { loadGacha } from "./loadGacha";
import { loadGarage } from "./loadGarage";
import { loadGeneralReward1, loadGeneralReward2 } from "./loadGeneralReward";
import { loadGhost } from "./loadGhost";
import { loadProfile2, loadProfile3 } from "./loadProfile";
import { loadRewardTable } from "./loadRewardTable";
import { checkTeamName1, checkTeamName2 } from "./checkTeamName";
import { createProfile1, createProfile2 } from "./createProfile";
import { createTeam1, createTeam2 } from "./createTeam";
import { createAutoTeam1, createAutoTeam2 } from "./createAutoTeam";
import { discoverProfile1, discoverProfile2 } from "./discoverProfile";
import {
load2on2RankingPoints1,
load2on2Info,
load2on2RankingPoints2,
} from "./load2on2";
import {
loadConfigA_1,
loadConfigB_1,
loadConfigA_2,
loadConfigB_2,
} from "./loadConfig";
import { loadEventInfo1, loadEventInfo2 } from "./loadEventInfo";
import { loadGacha1, loadGacha2 } from "./loadGacha";
import { loadGarage1, loadGarage2 } from "./loadGarage";
import {
loadGeneralReward1,
loadGeneralReward2,
loadGeneralReward3,
} from "./loadGeneralReward";
import { loadGhost1, loadGhost2 } from "./loadGhost";
import { loadProfile2, loadProfile3, loadProfile4 } from "./loadProfile";
import { loadRewardTable1, loadRewardTable2 } from "./loadRewardTable";
import { loadServerList } from "./loadServerList";
import { loadStocker } from "./loadStocker";
import { loadTeam } from "./loadTeam";
import { loadTeamRanking, loadTeamRanking2 } from "./loadTeamRanking";
import { loadStocker1, loadStocker2 } from "./loadStocker";
import { loadTeam1, loadTeam2 } from "./loadTeam";
import {
loadTeamRanking1,
loadTeamRanking2,
loadTeamRanking3,
loadTeamRanking4,
} from "./loadTeamRanking";
import { loadTopTen1 } from "./loadTopTen1";
import { loadTopTen2 } from "./loadTopTen2";
import { lockGarage } from "./lockGarage";
import { lockProfile } from "./lockProfile";
import { msg00AD } from "./msg00AD";
import { loadTopTen2, loadTopTen3 } from "./loadTopTen2";
import { lockGarage1, lockGarage2 } from "./lockGarage";
import { lockProfile1, lockProfile2 } from "./lockProfile";
import { saveExpedition1, saveExpedition2 } from "./saveExpedition";
import { saveGarage } from "./saveGarage";
import { saveNewCar } from "./saveNewCar";
import { saveGarage1, saveGarage2 } from "./saveGarage";
import { saveNewCar1, saveNewCar2 } from "./saveNewCar";
import { saveProfile2 } from "./saveProfile2";
import { saveProfile3 } from "./saveProfile3";
import { saveSettings } from "./saveSettings";
import { saveStocker } from "./saveStocker";
import { saveTeamBanner } from "./saveTeamBanner";
import { saveTimeAttack1, saveTimeAttack2 } from "./saveTimeAttack";
import { saveTopic } from "./saveTopic";
import { unlockProfile } from "./unlockProfile";
import { updateProvisionalStoreRank } from "./updateProvisionalStoreRank";
import { updateTeamLeader } from "./updateTeamLeader";
import { updateTeamMember } from "./updateTeamMember";
import { saveSettings1, saveSettings2 } from "./saveSettings";
import { saveStocker1, saveStocker2 } from "./saveStocker";
import { saveTeamBanner1, saveTeamBanner2 } from "./saveTeamBanner";
import {
saveTimeAttack1,
saveTimeAttack2,
saveTimeAttack3,
} from "./saveTimeAttack";
import { saveTopic1, saveTopic2 } from "./saveTopic";
import { unknownA_1, unknownA_2 } from "./unknownA";
import { unlockProfile1, unlockProfile2 } from "./unlockProfile";
import {
updateProvisionalStoreRank1,
updateProvisionalStoreRank2,
} from "./updateProvisionalStoreRank";
import { updateTeamLeader1, updateTeamLeader2 } from "./updateTeamLeader";
import { updateTeamMember1, updateTeamMember2 } from "./updateTeamMember";
import {
updateStoryClearNum1,
updateStoryClearNum2,
updateStoryClearNum3,
} from "./updateStoryClearNum";
import { Request } from "../request";
import { updateResult } from "./updateResult";
import { updateTeamPoints } from "./updateTeamPoints";
import { updateUiReport } from "./updateUiReport";
import { updateUserLog } from "./updateUserLog";
import { lockProfileExtend } from "./lockProfileExtend";
import { BLOCK_SIZE } from "../../common";
import { updateTeamPoints1, updateTeamPoints2 } from "./updateTeamPoints";
import { updateUiReport1, updateUiReport2 } from "./updateUiReport";
import { updateUserLog1, updateUserLog2 } from "./updateUserLog";
import { lockProfileExtend1, lockProfileExtend2 } from "./lockProfileExtend";
import { BLOCK_SIZE, ClientHello } from "../../common";
import { ByteStream } from "../../../util/stream";
import { saveProfile4 } from "./saveProfile4";
const debug = logger("app:idz:userdb:decoder");
@@ -58,69 +84,172 @@ export type ReaderFn = ((buf: Buffer) => Request) & {
msgLen: number;
};
const funcList: ReaderFn[] = [
checkTeamName,
createAutoTeam,
createProfile,
createTeam,
discoverProfile,
load2on2_v1,
load2on2_v2,
loadConfig,
loadConfig2,
loadEventInfo,
loadGacha,
loadGarage,
loadGeneralReward1,
loadGeneralReward2,
loadGhost,
loadProfile2,
loadProfile3,
loadRewardTable,
loadServerList,
loadStocker,
loadTeam,
loadTeamRanking,
loadTeamRanking2,
loadTopTen1,
loadTopTen2,
lockGarage,
lockProfile,
lockProfileExtend,
msg00AD,
saveExpedition1,
saveExpedition2,
saveGarage,
saveNewCar,
saveProfile2,
saveProfile3,
saveSettings,
saveStocker,
saveTeamBanner,
saveTimeAttack1,
saveTimeAttack2,
saveTopic,
unlockProfile,
updateProvisionalStoreRank,
updateResult,
updateStoryClearNum1,
updateStoryClearNum2,
updateTeamLeader,
updateTeamMember,
updateTeamPoints,
updateUiReport,
updateUserLog,
];
function makeReaderMap(funcList: ReaderFn[]): Map<number, ReaderFn> {
const result = new Map<number, ReaderFn>();
const readerFns = new Map<number, ReaderFn>();
const msgLengths = new Map<number, number>();
for (const fn of funcList) {
result.set(fn.msgCode, fn);
}
for (const fn of funcList) {
readerFns.set(fn.msgCode, fn);
msgLengths.set(fn.msgCode, fn.msgLen);
return result;
}
async function readRequest(stm: ByteStream): Promise<Request | undefined> {
// TODO confirm v1.21.00 proto version
const funcList110: ReaderFn[] = [
checkTeamName1,
createAutoTeam1,
createProfile1,
createTeam1,
discoverProfile1,
load2on2RankingPoints1,
loadConfigA_1,
loadConfigB_1,
loadEventInfo1,
loadGacha1,
loadGarage1,
loadGeneralReward1,
loadGhost1,
loadProfile2,
loadRewardTable1,
loadServerList,
loadStocker1,
loadTeam1,
loadTeamRanking1,
lockGarage1,
lockProfile1,
lockProfileExtend1,
loadTopTen1,
saveExpedition1,
saveGarage1,
saveNewCar1,
saveProfile2,
saveSettings1,
saveStocker1,
saveTeamBanner1,
saveTimeAttack1,
saveTopic1,
unknownA_1,
unlockProfile1,
updateProvisionalStoreRank1,
updateResult,
updateStoryClearNum1,
updateTeamLeader1,
updateTeamMember1,
updateTeamPoints1,
updateUiReport1,
updateUserLog1,
];
const funcList130: ReaderFn[] = [
checkTeamName1,
createAutoTeam1,
createProfile1,
createTeam1,
discoverProfile1,
load2on2Info,
updateStoryClearNum2,
updateStoryClearNum3,
loadConfigA_1,
loadConfigB_1,
loadEventInfo1,
loadGacha1,
loadGarage1,
loadGeneralReward3,
loadGhost1,
loadProfile3,
loadRewardTable1,
loadServerList,
loadStocker1,
loadTeam1,
loadTeamRanking1,
loadTeamRanking3,
loadTopTen2,
lockGarage1,
lockProfile1,
lockProfileExtend1,
saveExpedition2,
saveGarage1,
saveNewCar1,
saveProfile3,
saveSettings1,
saveStocker1,
saveTeamBanner1,
saveTimeAttack2,
saveTopic1,
unknownA_1,
unlockProfile1,
updateProvisionalStoreRank1,
updateResult,
updateTeamLeader1,
updateTeamMember1,
updateTeamPoints1,
updateUiReport1,
updateUserLog1,
];
const funcList210: ReaderFn[] = [
checkTeamName2,
createAutoTeam2,
createProfile2,
createTeam2,
discoverProfile2,
load2on2Info,
load2on2RankingPoints2,
updateStoryClearNum3,
loadConfigA_2,
loadConfigB_2,
loadEventInfo2,
loadGacha2,
loadGarage2,
loadGeneralReward3,
loadGhost2,
loadProfile4,
loadRewardTable2,
loadServerList,
loadStocker2,
loadTeam2,
loadTeamRanking2,
loadTeamRanking4,
loadTopTen3,
lockGarage2,
lockProfile2,
lockProfileExtend2,
saveExpedition2,
saveGarage2,
saveNewCar2,
saveProfile4,
saveSettings2,
saveStocker2,
saveTeamBanner2,
saveTimeAttack3,
saveTopic2,
unknownA_2,
unlockProfile2,
updateProvisionalStoreRank2,
updateResult,
updateTeamLeader2,
updateTeamMember2,
updateTeamPoints2,
updateUiReport2,
updateUserLog2,
];
const protocols = new Map<string, Map<number, ReaderFn>>();
protocols.set("110", makeReaderMap(funcList110));
protocols.set("130", makeReaderMap(funcList130));
protocols.set("210", makeReaderMap(funcList210));
async function readRequest(
clientHello: ClientHello,
stm: ByteStream
): Promise<Request | undefined> {
const protocol = protocols.get(clientHello.protocol);
if (protocol === undefined) {
throw new Error(`Unsupported protocol version ${clientHello.protocol}`);
}
const head = await stm.read(BLOCK_SIZE);
if (head.length === 0) {
@@ -129,16 +258,16 @@ async function readRequest(stm: ByteStream): Promise<Request | undefined> {
}
const msgCode = head.readUInt16LE(0x0000);
const msgLen = msgLengths.get(msgCode);
const readerFn = protocol.get(msgCode);
if (msgLen === undefined) {
if (readerFn === undefined) {
throw new Error(`Message ${msgCode.toString(16)}: Unknown command code`);
}
const tail = await stm.read(msgLen - BLOCK_SIZE);
const tail = await stm.read(readerFn.msgLen - BLOCK_SIZE);
const msg = Buffer.concat([head, tail]);
if (msg.length < msgLen) {
if (msg.length < readerFn.msgLen) {
throw new Error(`Message ${msgCode.toString(16)}: Truncated read`);
}
@@ -146,22 +275,19 @@ async function readRequest(stm: ByteStream): Promise<Request | undefined> {
debug("Raw: %s", msg.toString("hex"));
}
const reader = readerFns.get(msgCode);
if (reader === undefined) {
throw new Error(`Message ${msgCode.toString(16)}: No read handler`);
}
const payload = reader(msg);
const payload = readerFn(msg);
debug("Payload: %j", payload);
return payload;
}
export default async function* readRequestStream(stm: ByteStream) {
export default async function* readRequestStream(
clientHello: ClientHello,
stm: ByteStream
) {
while (true) {
const req = await readRequest(stm);
const req = await readRequest(clientHello, stm);
if (req === undefined) {
return;
+28 -11
View File
@@ -1,28 +1,45 @@
import { ExtId } from "../model/base";
import { Team } from "../model/team";
import { Load2on2Request1, Load2on2Request2 } from "../request/load2on2";
import {
Load2on2InfoRequest,
Load2on2RankingPointsRequest,
} from "../request/load2on2";
import { AimeId } from "../../../model";
load2on2_v1.msgCode = 0x00b0;
load2on2_v1.msgLen = 0x0010;
load2on2RankingPoints1.msgCode = 0x00b0;
load2on2RankingPoints1.msgLen = 0x0010;
export function load2on2_v1(buf: Buffer): Load2on2Request1 {
export function load2on2RankingPoints1(
buf: Buffer
): Load2on2RankingPointsRequest {
return {
type: "load_2on2_req",
format: 1,
type: "load_2on2_ranking_points_req",
field_0002: buf.readUInt16LE(0x0002),
aimeId: buf.readUInt32LE(0x0004) as AimeId,
teamId: buf.readUInt32LE(0x0008) as ExtId<Team>,
};
}
load2on2_v2.msgCode = 0x0132;
load2on2_v2.msgLen = 0x0010;
load2on2RankingPoints2.msgCode = 0x00a3;
load2on2RankingPoints2.msgLen = 0x0010;
export function load2on2_v2(buf: Buffer): Load2on2Request2 {
export function load2on2RankingPoints2(
buf: Buffer
): Load2on2RankingPointsRequest {
return {
type: "load_2on2_req",
format: 2,
type: "load_2on2_ranking_points_req",
field_0002: buf.readUInt16LE(0x0002),
aimeId: buf.readUInt32LE(0x0004) as AimeId,
teamId: buf.readUInt32LE(0x0008) as ExtId<Team>,
};
}
load2on2Info.msgCode = 0x0132;
load2on2Info.msgLen = 0x0010;
export function load2on2Info(buf: Buffer): Load2on2InfoRequest {
return {
type: "load_2on2_info_req",
field_0002: buf.readUInt16LE(0x0002),
aimeId: buf.readUInt32LE(0x0004) as AimeId,
teamId: buf.readUInt32LE(0x0008) as ExtId<Team>,
+26 -5
View File
@@ -1,8 +1,29 @@
import { LoadConfigRequest } from "../request/loadConfig";
import { LoadConfigRequestA, LoadConfigRequestB } from "../request/loadConfig";
loadConfig.msgCode = 0x0004;
loadConfig.msgLen = 0x0050;
loadConfigA_1.msgCode = 0x0004;
loadConfigA_1.msgLen = 0x0050;
export function loadConfig(): LoadConfigRequest {
return { type: "load_config_req" };
export function loadConfigA_1(): LoadConfigRequestA {
return { type: "load_config_A_req" };
}
loadConfigB_1.msgCode = 0x00ab;
loadConfigB_1.msgLen = 0x0010;
export function loadConfigB_1(): LoadConfigRequestB {
return { type: "load_config_B_req" };
}
loadConfigA_2.msgCode = 0x0004;
loadConfigA_2.msgLen = 0x0050;
export function loadConfigA_2(): LoadConfigRequestA {
return { type: "load_config_A_req" };
}
loadConfigB_2.msgCode = 0x00a0;
loadConfigB_2.msgLen = 0x0010;
export function loadConfigB_2(): LoadConfigRequestB {
return { type: "load_config_B_req" };
}
-8
View File
@@ -1,8 +0,0 @@
import { LoadConfigRequest2 } from "../request/loadConfig2";
loadConfig2.msgCode = 0x00ab;
loadConfig2.msgLen = 0x0010;
export function loadConfig2(): LoadConfigRequest2 {
return { type: "load_config_v2_req" };
}
+13 -3
View File
@@ -1,10 +1,20 @@
import { LoadEventInfoRequest } from "../request/loadEventInfo";
import { AimeId } from "../../../model";
loadEventInfo.msgCode = 0x00be;
loadEventInfo.msgLen = 0x0010;
loadEventInfo1.msgCode = 0x00be;
loadEventInfo1.msgLen = 0x0010;
export function loadEventInfo(buf: Buffer): LoadEventInfoRequest {
export function loadEventInfo1(buf: Buffer): LoadEventInfoRequest {
return {
type: "load_event_info_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
};
}
loadEventInfo2.msgCode = 0x00ac;
loadEventInfo2.msgLen = 0x0010;
export function loadEventInfo2(buf: Buffer): LoadEventInfoRequest {
return {
type: "load_event_info_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
+13 -3
View File
@@ -1,10 +1,20 @@
import { LoadGachaRequest } from "../request/loadGacha";
import { AimeId } from "../../../model";
loadGacha.msgCode = 0x00c1;
loadGacha.msgLen = 0x0010;
loadGacha1.msgCode = 0x00c1;
loadGacha1.msgLen = 0x0010;
export function loadGacha(buf: Buffer): LoadGachaRequest {
export function loadGacha1(buf: Buffer): LoadGachaRequest {
return {
type: "load_gacha_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
};
}
loadGacha2.msgCode = 0x00af;
loadGacha2.msgLen = 0x0010;
export function loadGacha2(buf: Buffer): LoadGachaRequest {
return {
type: "load_gacha_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
+17 -3
View File
@@ -1,13 +1,27 @@
import { LoadGarageRequest } from "../request/loadGarage";
import { AimeId } from "../../../model";
loadGarage.msgCode = 0x0090;
loadGarage.msgLen = 0x0010;
loadGarage1.msgCode = 0x0090;
loadGarage1.msgLen = 0x0010;
export function loadGarage(buf: Buffer): LoadGarageRequest {
export function loadGarage1(buf: Buffer): LoadGarageRequest {
return {
type: "load_garage_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 1,
fetchOffset: buf.readUInt8(0x0008),
field_000A: buf.readUInt16LE(0x000a),
};
}
loadGarage2.msgCode = 0x0087;
loadGarage2.msgLen = 0x0010;
export function loadGarage2(buf: Buffer): LoadGarageRequest {
return {
type: "load_garage_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 2,
fetchOffset: buf.readUInt8(0x0008),
field_000A: buf.readUInt16LE(0x000a),
};
+14 -9
View File
@@ -1,27 +1,32 @@
import {
LoadGeneralRewardRequest1,
LoadGeneralRewardRequest2,
} from "../request/loadGeneralReward";
import { LoadGeneralRewardRequest } from "../request/loadGeneralReward";
import { AimeId } from "../../../model";
loadGeneralReward1.msgCode = 0x009c;
loadGeneralReward1.msgLen = 0x0010;
export function loadGeneralReward1(buf: Buffer): LoadGeneralRewardRequest1 {
export function loadGeneralReward1(buf: Buffer): LoadGeneralRewardRequest {
return {
type: "load_general_reward_req",
format: 1,
aimeId: buf.readUInt32LE(0x0004) as AimeId,
};
}
loadGeneralReward2.msgCode = 0x013b;
loadGeneralReward2.msgCode = 0x093b;
loadGeneralReward2.msgLen = 0x0010;
export function loadGeneralReward2(buf: Buffer): LoadGeneralRewardRequest2 {
export function loadGeneralReward2(buf: Buffer): LoadGeneralRewardRequest {
return {
type: "load_general_reward_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
};
}
loadGeneralReward3.msgCode = 0x013b;
loadGeneralReward3.msgLen = 0x0010;
export function loadGeneralReward3(buf: Buffer): LoadGeneralRewardRequest {
return {
type: "load_general_reward_req",
format: 2,
aimeId: buf.readUInt32LE(0x0004) as AimeId,
};
}
+17 -3
View File
@@ -1,9 +1,23 @@
import { LoadGhostRequest } from "../request/loadGhost";
loadGhost.msgCode = 0x00a0;
loadGhost.msgLen = 0x0010;
loadGhost1.msgCode = 0x00a0;
loadGhost1.msgLen = 0x0010;
export function loadGhost(buf: Buffer): LoadGhostRequest {
export function loadGhost1(buf: Buffer): LoadGhostRequest {
return {
type: "load_ghost_req",
field_0002: buf.readUInt16LE(0x0002),
field_0004: buf.readUInt16LE(0x0004),
field_0008: buf.readUInt32LE(0x0008),
field_000C: buf.readUInt16LE(0x000c),
field_000E: buf.readUInt16LE(0x000e),
};
}
loadGhost2.msgCode = 0x0095;
loadGhost2.msgLen = 0x0010;
export function loadGhost2(buf: Buffer): LoadGhostRequest {
return {
type: "load_ghost_req",
field_0002: buf.readUInt16LE(0x0002),
+21 -11
View File
@@ -1,29 +1,39 @@
import {
LoadProfileRequest2,
LoadProfileRequest3,
} from "../request/loadProfile";
import { LoadProfileRequest } from "../request/loadProfile";
import { AimeId } from "../../../model";
import { readAsciiStr } from "../../util/bin";
loadProfile2.msgCode = 0x0067;
loadProfile2.msgLen = 0x0020;
export function loadProfile2(buf: Buffer): LoadProfileRequest2 {
export function loadProfile2(buf: Buffer): LoadProfileRequest {
return {
type: "load_profile_req",
format: 2,
aimeId: buf.readUInt32LE(0x0004) as AimeId,
luid: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
version: 1,
luid: readAsciiStr(buf, 0x0008, 0x0020),
};
}
loadProfile3.msgCode = 0x0012f;
loadProfile3.msgCode = 0x012f;
loadProfile3.msgLen = 0x0020;
export function loadProfile3(buf: Buffer): LoadProfileRequest3 {
export function loadProfile3(buf: Buffer): LoadProfileRequest {
return {
type: "load_profile_req",
format: 3,
aimeId: buf.readUInt32LE(0x0004) as AimeId,
luid: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
version: 1,
luid: readAsciiStr(buf, 0x0008, 0x0020),
};
}
loadProfile4.msgCode = 0x012f;
loadProfile4.msgLen = 0x0020;
export function loadProfile4(buf: Buffer): LoadProfileRequest {
return {
type: "load_profile_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 2,
luid: readAsciiStr(buf, 0x0008, 0x0020),
};
}
+12 -3
View File
@@ -1,9 +1,18 @@
import { LoadRewardTableRequest } from "../request/loadRewardTable";
loadRewardTable.msgCode = 0x0086;
loadRewardTable.msgLen = 0x0010;
loadRewardTable1.msgCode = 0x0086;
loadRewardTable1.msgLen = 0x0010;
export function loadRewardTable(): LoadRewardTableRequest {
export function loadRewardTable1(): LoadRewardTableRequest {
return {
type: "load_reward_table_req",
};
}
loadRewardTable2.msgCode = 0x007f;
loadRewardTable2.msgLen = 0x0010;
export function loadRewardTable2(): LoadRewardTableRequest {
return {
type: "load_reward_table_req",
};
+15 -3
View File
@@ -1,12 +1,24 @@
import { LoadStockerRequest } from "../request/loadStocker";
import { AimeId } from "../../../model";
loadStocker.msgCode = 0x00a7;
loadStocker.msgLen = 0x0010;
loadStocker1.msgCode = 0x00a7;
loadStocker1.msgLen = 0x0010;
export function loadStocker(buf: Buffer): LoadStockerRequest {
export function loadStocker1(buf: Buffer): LoadStockerRequest {
return {
type: "load_stocker_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 1,
};
}
loadStocker2.msgCode = 0x009c;
loadStocker2.msgLen = 0x0010;
export function loadStocker2(buf: Buffer): LoadStockerRequest {
return {
type: "load_stocker_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 2,
};
}
+18 -3
View File
@@ -2,15 +2,30 @@ import { LoadTeamRequest } from "../request/loadTeam";
import { ExtId } from "../model/base";
import { Team } from "../model/team";
loadTeam.msgCode = 0x0077;
loadTeam.msgLen = 0x0010;
loadTeam1.msgCode = 0x0077;
loadTeam1.msgLen = 0x0010;
export function loadTeam(buf: Buffer): LoadTeamRequest {
export function loadTeam1(buf: Buffer): LoadTeamRequest {
const extId = buf.readUInt32LE(0x0008);
return {
type: "load_team_req",
aimeId: buf.readUInt32LE(0x0004),
version: 1,
teamExtId: extId !== 0xffffffff ? (extId as ExtId<Team>) : undefined,
};
}
loadTeam2.msgCode = 0x0073;
loadTeam2.msgLen = 0x0010;
export function loadTeam2(buf: Buffer): LoadTeamRequest {
const extId = buf.readUInt32LE(0x0008);
return {
type: "load_team_req",
aimeId: buf.readUInt32LE(0x0004),
version: 2,
teamExtId: extId !== 0xffffffff ? (extId as ExtId<Team>) : undefined,
};
}
+25 -6
View File
@@ -1,17 +1,16 @@
import { LoadTeamRankingRequest } from "../request/loadTeamRanking";
loadTeamRanking.msgCode = 0x00b9;
loadTeamRanking.msgLen = 0x0010;
loadTeamRanking1.msgCode = 0x00b9;
loadTeamRanking1.msgLen = 0x0010;
export function loadTeamRanking(buf: Buffer): LoadTeamRankingRequest {
export function loadTeamRanking1(buf: Buffer): LoadTeamRankingRequest {
return {
type: "load_team_ranking_req",
};
}
// not sure what the difference is...
loadTeamRanking2.msgCode = 0x00bb;
// unused? Not sure where this came from.
loadTeamRanking2.msgCode = 0x00a7;
loadTeamRanking2.msgLen = 0x0010;
export function loadTeamRanking2(buf: Buffer): LoadTeamRankingRequest {
@@ -19,3 +18,23 @@ export function loadTeamRanking2(buf: Buffer): LoadTeamRankingRequest {
type: "load_team_ranking_req",
};
}
// not sure what the difference is...
loadTeamRanking3.msgCode = 0x00bb;
loadTeamRanking3.msgLen = 0x0010;
export function loadTeamRanking3(buf: Buffer): LoadTeamRankingRequest {
return {
type: "load_team_ranking_req",
};
}
loadTeamRanking4.msgCode = 0x00a9;
loadTeamRanking4.msgLen = 0x0010;
export function loadTeamRanking4(buf: Buffer): LoadTeamRankingRequest {
return {
type: "load_team_ranking_req",
};
}
+1
View File
@@ -24,6 +24,7 @@ export function loadTopTen1(buf: Buffer): LoadTopTenRequest {
return {
type: "load_top_ten_req",
version: 1,
field_2: buf.readUInt16LE(0x0002), // Bitmask selector
selectors,
field_C4: buf.readUInt8(0x00c4), // Boolean, true if profile ID is set
+32
View File
@@ -24,6 +24,7 @@ export function loadTopTen2(buf: Buffer): LoadTopTenRequest {
return {
type: "load_top_ten_req",
version: 1,
field_2: buf.readUInt16LE(0x0002), // Bitmask selector
selectors,
field_C4: buf.readUInt8(0x00f4), // Boolean, true if profile ID is set
@@ -33,3 +34,34 @@ export function loadTopTen2(buf: Buffer): LoadTopTenRequest {
teamId: teamId !== 0xffffffff ? (teamId as ExtId<Team>) : undefined,
};
}
loadTopTen3.msgCode = 0x012c;
loadTopTen3.msgLen = 0x0110;
export function loadTopTen3(buf: Buffer): LoadTopTenRequest {
const selectors = new Array<LoadTopTenRequestSelector>();
for (let i = 0; i < 40; i++) {
selectors.push({
routeNo: (buf.readUInt16LE(0x0004 + 2 + 2 * i) >> 1) as RouteNo,
minTimestamp: new Date(
buf.readUInt32LE(0x0054 + 4 + 4 * i) * 1000 + 1000
),
});
}
const profileId = buf.readUInt32LE(0x00fc);
const teamId = buf.readUInt32LE(0x0100);
return {
type: "load_top_ten_req",
version: 2,
field_2: buf.readUInt16LE(0x0004), // Bitmask selector
selectors,
field_C4: buf.readUInt8(0x00f8), // Boolean, true if profile ID is set
field_C5: buf.readUInt8(0x00f9), // Always zero
field_C6: buf.readUInt16LE(0x00fa),
aimeId: profileId !== 0 ? (profileId as AimeId) : undefined,
teamId: teamId !== 0xffffffff ? (teamId as ExtId<Team>) : undefined,
};
}
+13 -3
View File
@@ -1,9 +1,19 @@
import { LockGarageRequest } from "../request/lockGarage";
lockGarage.msgCode = 0x00a9;
lockGarage.msgLen = 0x0010;
lockGarage1.msgCode = 0x00a9;
lockGarage1.msgLen = 0x0010;
export function lockGarage(buf: Buffer): LockGarageRequest {
export function lockGarage1(buf: Buffer): LockGarageRequest {
return {
type: "lock_garage_request",
field_0004: buf.readUInt32LE(0x0004),
};
}
lockGarage2.msgCode = 0x009e;
lockGarage2.msgLen = 0x0010;
export function lockGarage2(buf: Buffer): LockGarageRequest {
return {
type: "lock_garage_request",
field_0004: buf.readUInt32LE(0x0004),
+17 -4
View File
@@ -1,13 +1,26 @@
import { readAsciiStr } from "../../util/bin";
import { LockProfileRequest } from "../request/lockProfile";
lockProfile.msgCode = 0x0069;
lockProfile.msgLen = 0x0020;
lockProfile1.msgCode = 0x0069;
lockProfile1.msgLen = 0x0020;
export function lockProfile(buf: Buffer): LockProfileRequest {
export function lockProfile1(buf: Buffer): LockProfileRequest {
return {
type: "lock_profile_req",
aimeId: buf.readUInt32LE(0x0004),
pcbId: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
pcbId: readAsciiStr(buf, 0x0008, 0x0018),
field_0018: buf.readUInt16LE(0x0018),
};
}
lockProfile2.msgCode = 0x0065;
lockProfile2.msgLen = 0x0020;
export function lockProfile2(buf: Buffer): LockProfileRequest {
return {
type: "lock_profile_req",
aimeId: buf.readUInt32LE(0x0004),
pcbId: readAsciiStr(buf, 0x0008, 0x0018),
field_0018: buf.readUInt16LE(0x0018),
};
}
+15 -3
View File
@@ -1,10 +1,22 @@
import { LockProfileExtendRequest } from "../request/lockProfileExtend";
import { AimeId } from "../../../model";
import { readAsciiStr } from "../../util/bin";
lockProfileExtend.msgCode = 0x006d;
lockProfileExtend.msgLen = 0x0020;
lockProfileExtend1.msgCode = 0x006d;
lockProfileExtend1.msgLen = 0x0020;
export function lockProfileExtend(buf: Buffer): LockProfileExtendRequest {
export function lockProfileExtend1(buf: Buffer): LockProfileExtendRequest {
return {
type: "lock_profile_extend_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
luid: readAsciiStr(buf, 0x0008, 0x0020),
};
}
lockProfileExtend2.msgCode = 0x0069;
lockProfileExtend2.msgLen = 0x0020;
export function lockProfileExtend2(buf: Buffer): LockProfileExtendRequest {
return {
type: "lock_profile_extend_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
-8
View File
@@ -1,8 +0,0 @@
import { Message00AD } from "../request/msg00AD";
msg00AD.msgCode = 0x00ad;
msg00AD.msgLen = 0x0620;
export function msg00AD(buf: Buffer): Message00AD {
return { type: "msg_00AD_req" };
}
+3 -8
View File
@@ -1,15 +1,11 @@
import {
SaveExpeditionRequest1,
SaveExpeditionRequest2,
} from "../request/saveExpedition";
import { SaveExpeditionRequest } from "../request/saveExpedition";
saveExpedition1.msgCode = 0x008c;
saveExpedition1.msgLen = 0x0010;
export function saveExpedition1(buf: Buffer): SaveExpeditionRequest1 {
export function saveExpedition1(buf: Buffer): SaveExpeditionRequest {
return {
type: "save_expedition_req",
format: 1,
field_0004: buf.readUInt32LE(0x0004),
};
}
@@ -17,10 +13,9 @@ export function saveExpedition1(buf: Buffer): SaveExpeditionRequest1 {
saveExpedition2.msgCode = 0x013f;
saveExpedition2.msgLen = 0x0010;
export function saveExpedition2(buf: Buffer): SaveExpeditionRequest2 {
export function saveExpedition2(buf: Buffer): SaveExpeditionRequest {
return {
type: "save_expedition_req",
format: 2,
field_0004: buf.readUInt32LE(0x0004),
};
}
+28 -5
View File
@@ -1,10 +1,11 @@
import { car } from "./_car";
import { SaveGarageRequest } from "../request/saveGarage";
import { AimeId } from "../../../model";
saveGarage.msgCode = 0x008e;
saveGarage.msgLen = 0x0090;
saveGarage1.msgCode = 0x008e;
saveGarage1.msgLen = 0x0090;
export function saveGarage(buf: Buffer): SaveGarageRequest {
export function saveGarage1(buf: Buffer): SaveGarageRequest {
const field_0068: number[] = [];
for (let offset = 0x0068; offset < 0x007c; offset += 2) {
@@ -13,10 +14,32 @@ export function saveGarage(buf: Buffer): SaveGarageRequest {
return {
type: "save_garage_req",
aimeId: buf.readUInt32LE(0x0004),
payload: car(buf.slice(0x0008, 0x0068)),
version: 1,
aimeId: buf.readUInt32LE(0x0004) as AimeId,
car: car(buf.slice(0x0008, 0x0068)),
field_0068,
field_0080: buf.readUInt8(0x0080),
field_0081: buf.readUInt8(0x0081) !== 0,
};
}
saveGarage2.msgCode = 0x0085;
saveGarage2.msgLen = 0x0090;
export function saveGarage2(buf: Buffer): SaveGarageRequest {
const field_0068: number[] = [];
for (let offset = 0x0068; offset < 0x007c; offset += 2) {
field_0068.push(buf.readUInt16LE(offset));
}
return {
type: "save_garage_req",
version: 2,
aimeId: buf.readUInt32LE(0x0004) as AimeId,
car: car(buf.slice(0x0008, 0x0068)),
field_0068,
field_0080: buf.readUInt8(0x0088),
field_0081: buf.readUInt8(0x0089) !== 0,
};
}
+20 -4
View File
@@ -1,15 +1,31 @@
import { car } from "./_car";
import { SaveNewCarRequest } from "../request/saveNewCar";
import { readAsciiStr } from "../../util/bin";
import { AimeId } from "../../../model";
saveNewCar.msgCode = 0x0079;
saveNewCar.msgLen = 0x0090;
saveNewCar1.msgCode = 0x0079;
saveNewCar1.msgLen = 0x0090;
export function saveNewCar(buf: Buffer): SaveNewCarRequest {
export function saveNewCar1(buf: Buffer): SaveNewCarRequest {
return {
type: "save_new_car_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
luid: buf.slice(0x0008, buf.indexOf(0, 0x0008)).toString("ascii"),
version: 1,
luid: readAsciiStr(buf, 0x0008, 0x001e),
car: car(buf.slice(0x0020, 0x0080)),
field_0080: buf.readUInt32LE(0x0080),
};
}
saveNewCar2.msgCode = 0x0075;
saveNewCar2.msgLen = 0x0090;
export function saveNewCar2(buf: Buffer): SaveNewCarRequest {
return {
type: "save_new_car_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 2,
luid: readAsciiStr(buf, 0x0008, 0x001e),
car: car(buf.slice(0x0020, 0x0080)),
field_0080: buf.readUInt32LE(0x0080),
};
+11 -8
View File
@@ -1,31 +1,33 @@
import { car } from "./_car";
import { mission } from "./_mission";
import { BackgroundCode, CourseNo, TitleCode } from "../model/base";
import { SaveProfileRequest2 } from "../request/saveProfile";
import { StoryCell, StoryRow } from "../model/story";
import { SaveProfileRequest } from "../request/saveProfile";
import { bitmap } from "./_bitmap";
import { AimeId } from "../../../model";
saveProfile2.msgCode = 0x0068;
saveProfile2.msgLen = 0x0940;
export function saveProfile2(buf: Buffer): SaveProfileRequest2 {
const storyRows = new Array();
export function saveProfile2(buf: Buffer): SaveProfileRequest {
const storyRows = new Map<number, StoryRow>();
for (let i = 0; i < 9; i++) {
const cells = new Array();
const cells = new Map<number, StoryCell>();
const rowOffset = 0x01a8 + i * 0x3c;
for (let j = 0; j < 9; j++) {
const a = buf.readUInt32LE(rowOffset + 0x04 + j * 4);
const b = buf.readUInt16LE(rowOffset + 0x28 + j * 2);
const cell = { a, b };
const c = 0; // Added in idz2
const cell = { a, b, c };
cells.push(cell);
cells.set(j, cell);
}
const row = { cells };
storyRows.push(row);
storyRows.set(i, row);
}
const coursePlays = new Map<CourseNo, number>();
@@ -45,8 +47,8 @@ export function saveProfile2(buf: Buffer): SaveProfileRequest2 {
return {
type: "save_profile_req",
format: 2,
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 1,
lv: buf.readUInt16LE(0x0026),
exp: buf.readUInt32LE(0x0028),
fame: buf.readUInt32LE(0x0468),
@@ -94,6 +96,7 @@ export function saveProfile2(buf: Buffer): SaveProfileRequest2 {
aura: buf.readUInt8(0x002c),
paperCup: buf.readUInt8(0x00f6),
gauges: buf.readUInt8(0x00f7),
drivingStyle: 0, // Not supported until idz2
},
};
}
+11 -8
View File
@@ -1,33 +1,35 @@
import { car } from "./_car";
import { mission } from "./_mission";
import { BackgroundCode, CourseNo, TitleCode } from "../model/base";
import { SaveProfileRequest2 } from "../request/saveProfile";
import { StoryCell, StoryRow } from "../model/story";
import { SaveProfileRequest } from "../request/saveProfile";
import { bitmap } from "./_bitmap";
import { AimeId } from "../../../model";
saveProfile3.msgCode = 0x0138;
saveProfile3.msgLen = 0x0a70;
export function saveProfile3(buf: Buffer): SaveProfileRequest2 {
const storyRows = new Array();
export function saveProfile3(buf: Buffer): SaveProfileRequest {
const storyRows = new Map<number, StoryRow>();
// Story layout has changed somewhat...
for (let i = 0; i < 27; i++) {
const cells = new Array();
const cells = new Map<number, StoryCell>();
const rowOffset = 0x01ac + i * 0x18;
for (let j = 0; j < 9; j++) {
const a = buf.readUInt8(rowOffset + 0x00 + j);
const b = buf.readUInt8(rowOffset + 0x09 + j);
const cell = { a, b };
const c = 0; // Added in idz2
const cell = { a, b, c };
cells.push(cell);
cells.set(j, cell);
}
const row = { cells };
storyRows.push(row);
storyRows.set(i, row);
}
const coursePlays = new Map<CourseNo, number>();
@@ -47,8 +49,8 @@ export function saveProfile3(buf: Buffer): SaveProfileRequest2 {
return {
type: "save_profile_req",
format: 2,
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 1,
lv: buf.readUInt16LE(0x0026),
exp: buf.readUInt32LE(0x0028),
fame: buf.readUInt32LE(0x04fc),
@@ -96,6 +98,7 @@ export function saveProfile3(buf: Buffer): SaveProfileRequest2 {
aura: buf.readUInt8(0x002c),
paperCup: buf.readUInt8(0x00f6),
gauges: buf.readUInt8(0x00f7),
drivingStyle: 0, // Not supported until idz2
},
};
}
+127
View File
@@ -0,0 +1,127 @@
import { car } from "./_car";
import { mission } from "./_mission";
import { BackgroundCode, CourseNo, StampCode, TitleCode } from "../model/base";
import { StoryCell, StoryRow } from "../model/story";
import { SaveProfileRequest } from "../request/saveProfile";
import { bitmap } from "./_bitmap";
import { AimeId } from "../../../model";
saveProfile4.msgCode = 0x0138;
saveProfile4.msgLen = 0x0eb0;
export function saveProfile4(buf: Buffer): SaveProfileRequest {
const storyRows = new Map<number, StoryRow>();
// Story layout got another rework in idz2
// First two bytes per row denote the chapter, however
// since this is in completely linear order (in 2.12) we can
// just skip saving/reading this for our convenience.
for (let i = 0; i < 6; i++) {
const cells = new Map<number, StoryCell>();
const rowOffset = 0x026c + 0x02 + i * 0x7a;
for (let j = 0; j < 28; j++) {
const a = buf.readUInt8(rowOffset + 0x00 + j * 4);
const b = buf.readUInt8(rowOffset + 0x01 + j * 4);
const c = buf.readUInt8(rowOffset + 0x02 + j * 4);
const cell = { a, b, c };
cells.set(j, cell);
}
const row = { cells };
storyRows.set(i, row);
}
const coursePlays = new Map<CourseNo, number>();
for (let i = 0; i < 20; i++) {
coursePlays.set(i as CourseNo, buf.readUInt16LE(0x07dc + 2 * i));
}
const freeCar = {
validFrom: buf.readUInt32LE(0x01fc),
};
const freeContinue = {
validFrom: buf.readUInt32LE(0x0038),
validTo: buf.readUInt32LE(0x003c),
};
const weeklyReset = {
endDate: buf.readUInt32LE(0x0c90),
};
return {
type: "save_profile_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 2,
lv: buf.readUInt16LE(0x0026),
exp: buf.readUInt32LE(0x0028),
fame: buf.readUInt32LE(0x0784),
dpoint: buf.readUInt32LE(0x0780),
mileage: buf.readUInt32LE(0x0008),
title: buf.readUInt16LE(0x0040) as TitleCode,
titles: bitmap(buf.slice(0x0042, 0x01b9)),
background: buf.readUInt8(0x0afc) as BackgroundCode,
coursePlays,
missions: {
team: mission(buf.slice(0x06c0, 0x06e4)),
solo: mission(buf.slice(0x0ad0, 0x0af2)),
},
car: car(buf.slice(0x0b80, 0x0be0)),
story: {
x: buf.readUInt16LE(0x0aa0),
y: buf.readUInt8(0x0a84),
rows: storyRows,
},
unlocks: {
auras: buf.readUInt16LE(0x01d0),
cup: buf.readUInt8(0x01d4),
gauges: buf.readUInt32LE(0x01d8),
music: buf.readUInt16LE(0x0204),
lastMileageReward: buf.readUInt32LE(0x0200),
},
tickets: {
freeCar:
freeCar.validFrom !== 0
? {
validFrom: new Date(freeCar.validFrom * 1000),
}
: undefined,
freeContinue:
freeContinue.validFrom !== 0 && freeContinue.validTo !== 0
? {
validFrom: new Date(freeContinue.validFrom * 1000),
validTo: new Date(freeContinue.validTo * 1000),
}
: undefined,
},
selectedStamps: {
stamp01: buf.readUInt16LE(0x0d74) as StampCode,
stamp02: buf.readUInt16LE(0x0d76) as StampCode,
stamp03: buf.readUInt16LE(0x0d78) as StampCode,
stamp04: buf.readUInt16LE(0x0d7a) as StampCode,
},
stamps: bitmap(buf.slice(0x0d28, 0x0d4e)),
settings: {
music: buf.readUInt16LE(0x0776),
pack: buf.readUInt32LE(0x0034),
aura: buf.readUInt8(0x002c),
paperCup: buf.readUInt8(0x01b9),
gauges: buf.readUInt8(0x01ba),
drivingStyle: buf.readUInt8(0x0dae),
},
weeklyMissions: {
weeklyReset: new Date(weeklyReset.endDate * 1000),
weeklyMissionLeft: buf.readUInt16LE(0x0c84),
weeklyProgressLeft: buf.readUInt16LE(0x0c86),
weeklyParamsLeft: buf.readUInt16LE(0x0c88),
weeklyMissionRight: buf.readUInt16LE(0x0c8a),
weeklyProgressRight: buf.readUInt16LE(0x0c8c),
weeklyParamsRight: buf.readUInt16LE(0x0c8e),
},
};
}
+26 -5
View File
@@ -1,15 +1,14 @@
import { SaveSettingsRequest } from "../request/saveSettings";
import { AimeId } from "../../../model";
saveSettings.msgCode = 0x00a5;
saveSettings.msgLen = 0x0020;
export function saveSettings(buf: Buffer): SaveSettingsRequest {
const pack = buf.readUInt32LE(0x000c);
saveSettings1.msgCode = 0x00a5;
saveSettings1.msgLen = 0x0020;
export function saveSettings1(buf: Buffer): SaveSettingsRequest {
return {
type: "save_settings_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 1,
dpoint: buf.readUInt32LE(0x0008),
settings: {
music: buf.readUInt16LE(0x0002),
@@ -17,7 +16,29 @@ export function saveSettings(buf: Buffer): SaveSettingsRequest {
paperCup: buf.readUInt8(0x0011),
gauges: buf.readUInt8(0x0012),
aura: buf.readUInt8(0x0013),
drivingStyle: 0, // Not supported until idz2
},
field_0010: buf.readUInt8(0x0010),
};
}
saveSettings2.msgCode = 0x009a;
saveSettings2.msgLen = 0x0020;
export function saveSettings2(buf: Buffer): SaveSettingsRequest {
return {
type: "save_settings_req",
aimeId: buf.readUInt32LE(0x0008) as AimeId,
version: 2,
dpoint: buf.readUInt32LE(0x000c),
settings: {
music: buf.readUInt16LE(0x0004),
pack: buf.readUInt32LE(0x0010),
paperCup: buf.readUInt8(0x0015),
gauges: buf.readUInt8(0x0016),
aura: buf.readUInt8(0x0017),
drivingStyle: buf.readUInt8(0x0018),
},
field_0010: buf.readUInt32LE(0x0014),
};
}
+27 -4
View File
@@ -1,19 +1,42 @@
import { bitmap } from "./_bitmap";
import { chara } from "./_chara";
import { StampCode } from "../model/base";
import { CarSelector } from "../model/car";
import { SaveStockerRequest } from "../request/saveStocker";
import { AimeId } from "../../../model";
saveStocker.msgCode = 0x00a6;
saveStocker.msgLen = 0x00c0;
saveStocker1.msgCode = 0x00a6;
saveStocker1.msgLen = 0x00c0;
export function saveStocker(buf: Buffer): SaveStockerRequest {
export function saveStocker1(buf: Buffer): SaveStockerRequest {
return {
type: "save_stocker_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 1,
backgrounds: bitmap(buf.slice(0x0008, 0x002c)),
selectedCar: buf.readUInt16LE(0x009c) as CarSelector,
chara: chara(buf.slice(0x009e, 0x00b2)),
};
}
saveStocker2.msgCode = 0x009b;
saveStocker2.msgLen = 0x0110;
export function saveStocker2(buf: Buffer): SaveStockerRequest {
return {
type: "save_stocker_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 2,
backgrounds: bitmap(buf.slice(0x0008, 0x0034)),
selectedCar: buf.readUInt16LE(0x009c) as CarSelector,
chara: chara(buf.slice(0x009e, 0x00b2)),
myChara: bitmap(buf.slice(0x0034, 0x098)),
selectedStamps: {
stamp01: buf.readUInt16LE(0x00da) as StampCode,
stamp02: buf.readUInt16LE(0x00dc) as StampCode,
stamp03: buf.readUInt16LE(0x00de) as StampCode,
stamp04: buf.readUInt16LE(0x00e0) as StampCode,
},
stamps: bitmap(buf.slice(0x00b3, 0x00d9)),
};
}
+17 -3
View File
@@ -2,13 +2,27 @@ import { SaveTeamBannerRequest } from "../request/saveTeamBanner";
import { ExtId } from "../model/base";
import { Team } from "../model/team";
saveTeamBanner.msgCode = 0x0089;
saveTeamBanner.msgLen = 0x0010;
saveTeamBanner1.msgCode = 0x0089;
saveTeamBanner1.msgLen = 0x0010;
export function saveTeamBanner(buf: Buffer): SaveTeamBannerRequest {
export function saveTeamBanner1(buf: Buffer): SaveTeamBannerRequest {
return {
type: "save_team_banner_req",
teamExtId: buf.readUInt32LE(0x0004) as ExtId<Team>,
version: 1,
nameBg: buf.readUInt32LE(0x0008),
nameFx: buf.readUInt32LE(0x000c),
};
}
saveTeamBanner2.msgCode = 0x0082;
saveTeamBanner2.msgLen = 0x0010;
export function saveTeamBanner2(buf: Buffer): SaveTeamBannerRequest {
return {
type: "save_team_banner_req",
teamExtId: buf.readUInt32LE(0x0004) as ExtId<Team>,
version: 2,
nameBg: buf.readUInt32LE(0x0008),
nameFx: buf.readUInt32LE(0x000c),
};
+33
View File
@@ -7,6 +7,7 @@ function saveTimeAttack(buf: Buffer): SaveTimeAttackRequest {
return {
type: "save_time_attack_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 1,
dayNight: buf.readUInt8(0x0054) & 1,
payload: {
routeNo: (buf.readUInt8(0x0054) >> 1) as RouteNo,
@@ -48,3 +49,35 @@ saveTimeAttack2.msgLen = 0x0080;
export function saveTimeAttack2(buf: Buffer): SaveTimeAttackRequest {
return saveTimeAttack(buf);
}
saveTimeAttack3.msgCode = 0x0136;
saveTimeAttack3.msgLen = 0x0080;
export function saveTimeAttack3(buf: Buffer): SaveTimeAttackRequest {
return {
type: "save_time_attack_req",
aimeId: buf.readUInt32LE(0x0008) as AimeId,
version: 2,
dayNight: buf.readUInt8(0x0058) & 1,
payload: {
routeNo: (buf.readUInt8(0x0058) >> 1) as RouteNo,
timestamp: new Date(buf.readUInt32LE(0x005c) * 1000),
flags: buf.readUInt8(0x0060),
totalTime: buf.readUInt32LE(0x001c) / 1000,
sectionTimes: [
buf.readUInt32LE(0x0028) / 1000,
buf.readUInt32LE(0x002c) / 1000,
buf.readUInt32LE(0x0030) / 1000,
],
grade: buf.readUInt8(0x0066),
carSelector: buf.readUInt16LE(0x0010) as CarSelector,
},
field_0002: buf.readUInt16LE(0x0004),
field_0008: buf.readUInt32LE(0x000c),
field_0012: buf.readUInt8(0x0016),
field_0015: buf.readUInt8(0x0019),
field_005D: buf.readUInt8(0x0061),
field_005E: buf.readUInt16LE(0x0062),
field_0060: buf.readUInt16LE(0x0064),
};
}
+15 -3
View File
@@ -1,9 +1,21 @@
import { SaveTopicRequest } from "../request/saveTopic";
saveTopic.msgCode = 0x009a;
saveTopic.msgLen = 0x0010;
saveTopic1.msgCode = 0x009a;
saveTopic1.msgLen = 0x0010;
export function saveTopic(buf: Buffer): SaveTopicRequest {
export function saveTopic1(buf: Buffer): SaveTopicRequest {
const aimeId = buf.readUInt32LE(0x0004);
return {
type: "save_topic_req",
aimeId: aimeId !== 0xffffffff ? aimeId : undefined,
};
}
saveTopic2.msgCode = 0x0091;
saveTopic2.msgLen = 0x0010;
export function saveTopic2(buf: Buffer): SaveTopicRequest {
const aimeId = buf.readUInt32LE(0x0004);
return {
+15
View File
@@ -0,0 +1,15 @@
import { UnknownRequestA } from "../request/unknownA";
unknownA_1.msgCode = 0x00ad;
unknownA_1.msgLen = 0x0620;
export function unknownA_1(buf: Buffer): UnknownRequestA {
return { type: "unknown_A_req" };
}
unknownA_2.msgCode = 0x00a2;
unknownA_2.msgLen = 0x0620;
export function unknownA_2(buf: Buffer): UnknownRequestA {
return { type: "unknown_A_req" };
}
+14 -3
View File
@@ -1,9 +1,20 @@
import { UnlockProfileRequest } from "../request/unlockProfile";
unlockProfile.msgCode = 0x006f;
unlockProfile.msgLen = 0x0020;
unlockProfile1.msgCode = 0x006f;
unlockProfile1.msgLen = 0x0020;
export function unlockProfile(buf: Buffer): UnlockProfileRequest {
export function unlockProfile1(buf: Buffer): UnlockProfileRequest {
return {
type: "unlock_profile_req",
aimeId: buf.readUInt32LE(0x0004),
pcbId: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
};
}
unlockProfile2.msgCode = 0x006b;
unlockProfile2.msgLen = 0x0020;
export function unlockProfile2(buf: Buffer): UnlockProfileRequest {
return {
type: "unlock_profile_req",
aimeId: buf.readUInt32LE(0x0004),
@@ -0,0 +1,13 @@
import { UpdateExpeditionRequest } from "../request/updateExpedition";
updateExpedition.msgCode = 0x013F;
updateExpedition.msgLen = 0x0010;
export function updateExpedition(
buf: Buffer
): UpdateExpeditionRequest {
return {
type: "update_expedition_req",
expeditionRequestType: buf.readUInt32LE(0x0004),
};
}
@@ -1,9 +1,21 @@
import { UpdateProvisionalStoreRankRequest } from "../request/updateProvisionalStoreRank";
updateProvisionalStoreRank.msgCode = 0x0082;
updateProvisionalStoreRank.msgLen = 0x0010;
updateProvisionalStoreRank1.msgCode = 0x0082;
updateProvisionalStoreRank1.msgLen = 0x0010;
export function updateProvisionalStoreRank(
export function updateProvisionalStoreRank1(
buf: Buffer
): UpdateProvisionalStoreRankRequest {
return {
type: "update_provisional_store_rank_req",
aimeId: buf.readUInt32LE(0x0004),
};
}
updateProvisionalStoreRank2.msgCode = 0x007c;
updateProvisionalStoreRank2.msgLen = 0x0010;
export function updateProvisionalStoreRank2(
buf: Buffer
): UpdateProvisionalStoreRankRequest {
return {
+13 -13
View File
@@ -1,28 +1,28 @@
import {
UpdateStoryClearNumRequest1,
UpdateStoryClearNumRequest2,
} from "../request/updateStoryClearNum";
import { UpdateStoryClearNumRequest } from "../request/updateStoryClearNum";
updateStoryClearNum1.msgCode = 0x007f;
updateStoryClearNum1.msgLen = 0x0010;
export function updateStoryClearNum1(
buf: Buffer
): UpdateStoryClearNumRequest1 {
export function updateStoryClearNum1(buf: Buffer): UpdateStoryClearNumRequest {
return {
type: "update_story_clear_num_req",
format: 1,
};
}
updateStoryClearNum2.msgCode = 0x013d;
updateStoryClearNum2.msgCode = 0x097f;
updateStoryClearNum2.msgLen = 0x0010;
export function updateStoryClearNum2(
buf: Buffer
): UpdateStoryClearNumRequest2 {
export function updateStoryClearNum2(buf: Buffer): UpdateStoryClearNumRequest {
return {
type: "update_story_clear_num_req",
};
}
updateStoryClearNum3.msgCode = 0x013d;
updateStoryClearNum3.msgLen = 0x0010;
export function updateStoryClearNum3(buf: Buffer): UpdateStoryClearNumRequest {
return {
type: "update_story_clear_num_req",
format: 2,
};
}
+19 -4
View File
@@ -2,15 +2,30 @@ import { ExtId } from "../model/base";
import { Team } from "../model/team";
import { UpdateTeamLeaderRequest } from "../request/updateTeamLeader";
import { AimeId } from "../../../model";
import { readAsciiStr } from "../../util/bin";
updateTeamLeader.msgCode = 0x008a;
updateTeamLeader.msgLen = 0x0020;
updateTeamLeader1.msgCode = 0x008a;
updateTeamLeader1.msgLen = 0x0020;
export function updateTeamLeader(buf: Buffer): UpdateTeamLeaderRequest {
export function updateTeamLeader1(buf: Buffer): UpdateTeamLeaderRequest {
return {
type: "update_team_leader_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 1,
teamExtId: buf.readUInt32LE(0x0008) as ExtId<Team>,
field_000C: buf.slice(0x000c, buf.indexOf("\0", 0x000c)).toString("ascii"),
field_000C: readAsciiStr(buf, 0x000c, 0x0020),
};
}
updateTeamLeader2.msgCode = 0x0083;
updateTeamLeader2.msgLen = 0x0020;
export function updateTeamLeader2(buf: Buffer): UpdateTeamLeaderRequest {
return {
type: "update_team_leader_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
version: 2,
teamExtId: buf.readUInt32LE(0x0008) as ExtId<Team>,
field_000C: readAsciiStr(buf, 0x000c, 0x0020),
};
}
+17 -3
View File
@@ -3,14 +3,28 @@ import { Team } from "../model/team";
import { UpdateTeamMemberRequest } from "../request/updateTeamMember";
import { AimeId } from "../../../model";
updateTeamMember.msgCode = 0x0073;
updateTeamMember.msgLen = 0x0010;
updateTeamMember1.msgCode = 0x0073;
updateTeamMember1.msgLen = 0x0010;
export function updateTeamMember(buf: Buffer): UpdateTeamMemberRequest {
export function updateTeamMember1(buf: Buffer): UpdateTeamMemberRequest {
return {
type: "update_team_member_req",
action: buf.readUInt8(0x0004) === 0 ? "add" : "remove",
aimeId: buf.readUInt32LE(0x0008) as AimeId,
version: 1,
teamExtId: buf.readUInt32LE(0x000c) as ExtId<Team>,
};
}
updateTeamMember2.msgCode = 0x006f;
updateTeamMember2.msgLen = 0x0010;
export function updateTeamMember2(buf: Buffer): UpdateTeamMemberRequest {
return {
type: "update_team_member_req",
action: buf.readUInt8(0x0004) === 0 ? "add" : "remove",
aimeId: buf.readUInt32LE(0x0008) as AimeId,
version: 2,
teamExtId: buf.readUInt32LE(0x000c) as ExtId<Team>,
};
}
+14 -3
View File
@@ -1,9 +1,20 @@
import { UpdateTeamPointsRequest } from "../request/updateTeamPoints";
updateTeamPoints.msgCode = 0x0081;
updateTeamPoints.msgLen = 0x0010;
updateTeamPoints1.msgCode = 0x0081;
updateTeamPoints1.msgLen = 0x0010;
export function updateTeamPoints(buf: Buffer): UpdateTeamPointsRequest {
export function updateTeamPoints1(buf: Buffer): UpdateTeamPointsRequest {
return {
type: "update_team_points_req",
field_0004: buf.readUInt32LE(0x0004),
field_0008: buf.readUInt32LE(0x0008),
};
}
updateTeamPoints2.msgCode = 0x007b;
updateTeamPoints2.msgLen = 0x0010;
export function updateTeamPoints2(buf: Buffer): UpdateTeamPointsRequest {
return {
type: "update_team_points_req",
field_0004: buf.readUInt32LE(0x0004),
+14 -3
View File
@@ -1,9 +1,20 @@
import { UpdateUiReportRequest } from "../request/updateUiReport";
updateUiReport.msgCode = 0x0084;
updateUiReport.msgLen = 0x0410;
updateUiReport1.msgCode = 0x0084;
updateUiReport1.msgLen = 0x0410;
export function updateUiReport(buf: Buffer): UpdateUiReportRequest {
export function updateUiReport1(buf: Buffer): UpdateUiReportRequest {
return {
type: "update_ui_report_req",
field_02: buf.readUInt16LE(0x0002),
field_04: buf.readUInt16LE(0x0004),
};
}
updateUiReport2.msgCode = 0x007e;
updateUiReport2.msgLen = 0x0410;
export function updateUiReport2(buf: Buffer): UpdateUiReportRequest {
return {
type: "update_ui_report_req",
field_02: buf.readUInt16LE(0x0002),
+10 -3
View File
@@ -1,8 +1,15 @@
import { UpdateUserLogRequest } from "../request/updateUserLog";
updateUserLog.msgCode = 0x00bd;
updateUserLog.msgLen = 0x0050;
updateUserLog1.msgCode = 0x00bd;
updateUserLog1.msgLen = 0x0050;
export function updateUserLog(buf: Buffer): UpdateUserLogRequest {
export function updateUserLog1(buf: Buffer): UpdateUserLogRequest {
return { type: "update_user_log_req" };
}
updateUserLog2.msgCode = 0x00ab;
updateUserLog2.msgLen = 0x0050;
export function updateUserLog2(buf: Buffer): UpdateUserLogRequest {
return { type: "update_user_log_req" };
}
+18 -1
View File
@@ -1,6 +1,6 @@
import { Chara } from "../model/chara";
export function encodeChara(chara: Chara): Buffer {
export function encodeChara1(chara: Chara): Buffer {
const buf = Buffer.alloc(0x0014);
buf.writeUInt8(chara.gender === "male" ? 0 : 1, 0x00);
@@ -16,3 +16,20 @@ export function encodeChara(chara: Chara): Buffer {
return buf;
}
export function encodeChara2(chara: Chara): Buffer {
const buf = Buffer.alloc(0x0016);
buf.writeUInt8(chara.gender === "male" ? 0 : 1, 0x00);
buf.writeUInt16LE(chara.field_02, 0x02);
buf.writeUInt16LE(chara.field_04, 0x04);
buf.writeUInt16LE(chara.field_06, 0x06);
buf.writeUInt16LE(chara.field_08, 0x08);
buf.writeUInt16LE(chara.field_0a, 0x0a);
buf.writeUInt16LE(chara.field_0c, 0x0c);
buf.writeUInt16LE(chara.field_0e, 0x0e);
buf.writeUInt16LE(chara.background, 0x10);
buf.writeUInt16LE(chara.title, 0x14);
return buf;
}
+82 -17
View File
@@ -1,23 +1,20 @@
import iconv from "iconv-lite";
import { writeSjisStr } from "../../util/bin";
import { CreateAutoTeamResponse } from "../response/createAutoTeam";
import { LoadTeamResponse } from "../response/loadTeam";
import { encodeChara } from "./_chara";
import { encodeChara1 } from "./_chara";
export function _team(res: CreateAutoTeamResponse | LoadTeamResponse) {
function _team1(
msgCode: number,
res: CreateAutoTeamResponse | LoadTeamResponse
) {
const buf = Buffer.alloc(0x0ca0);
if (res.type === "create_auto_team_res") {
buf.writeInt16LE(0x007c, 0x0000);
} else {
buf.writeInt16LE(0x0078, 0x0000);
}
const leader = res.members.find(item => item.leader === true);
buf.writeInt16LE(msgCode, 0x0000);
buf.writeUInt32LE(res.team.extId, 0x000c);
iconv.encode(leader ? leader.profile.name : "Error\0", "shift_jis").copy(buf, 0x0010);
iconv.encode(res.team.name, "shift_jis").copy(buf, 0x0024);
iconv.encode(process.env.SHOP_NAME ? process.env.SHOP_NAME : "\0", "shift_jis").copy(buf, 0x0044);
writeSjisStr(buf, 0x0010, 0x0024, leader ? leader.profile.name : "Error");
writeSjisStr(buf, 0x0024, 0x0044, res.team.name);
writeSjisStr(buf, 0x0044, 0x00d8, process.env.SHOP_NAME || "");
buf.writeUInt32LE(res.team.nameBg, 0x00d8);
buf.writeUInt32LE(res.team.nameFx, 0x00dc);
buf.fill(0xff, 0x00e0, 0x00f9); // Bitset: Unlocked BGs probably
@@ -25,7 +22,7 @@ export function _team(res: CreateAutoTeamResponse | LoadTeamResponse) {
buf.writeUInt32LE(leader ? leader.profile.aimeId : 0, 0x0080);
for (let i = 0; i < 6; i++) {
const base = 0x011c + i * 0x005c;
const base = 0x011c + i * 0x005c; // Differs between v1 and v2
const member = res.members[i];
if (member === undefined) {
@@ -36,11 +33,11 @@ export function _team(res: CreateAutoTeamResponse | LoadTeamResponse) {
const accessTime = (profile.accessTime.getTime() / 1000) | 0;
buf.writeInt32LE(profile.aimeId, base + 0x0000);
iconv.encode(profile.name + "\0", "shift_jis").copy(buf, base + 0x0004);
writeSjisStr(buf, base + 0x0004, base + 0x0018, profile.name);
buf.writeInt32LE(profile.lv, base + 0x0018);
buf.writeInt32LE(0, base + 0x0024); // Month points, TODO
buf.writeUInt32LE(accessTime, base + 0x0034);
encodeChara(chara).copy(buf, base + 0x0044);
encodeChara1(chara).copy(buf, base + 0x0044);
}
// Team Time Attack:
@@ -57,3 +54,71 @@ export function _team(res: CreateAutoTeamResponse | LoadTeamResponse) {
return buf;
}
function _team2(
msgCode: number,
res: CreateAutoTeamResponse | LoadTeamResponse
) {
const buf = Buffer.alloc(0x0ca0);
const leader = res.members.find(item => item.leader === true);
buf.writeInt16LE(msgCode, 0x0000);
buf.writeUInt32LE(res.team.extId, 0x000c);
writeSjisStr(buf, 0x0010, 0x0024, leader ? leader.profile.name : "Error");
writeSjisStr(buf, 0x0024, 0x0044, res.team.name);
writeSjisStr(buf, 0x0044, 0x00d8, process.env.SHOP_NAME || "");
buf.writeUInt32LE(res.team.nameBg, 0x00d8);
buf.writeUInt32LE(res.team.nameFx, 0x00dc);
buf.fill(0xff, 0x00e0, 0x00f9); // Bitset: Unlocked BGs probably
buf.fill(0xff, 0x00f9, 0x0101); // Bitset: Unlocked FX probably
buf.writeUInt32LE(leader ? leader.profile.aimeId : 0, 0x0080);
for (let i = 0; i < 6; i++) {
const base = 0x0120 + i * 0x005c; // Differs between v1 and v2
const member = res.members[i];
if (member === undefined) {
break;
}
const { profile, chara } = member;
const accessTime = (profile.accessTime.getTime() / 1000) | 0;
buf.writeInt32LE(profile.aimeId, base + 0x0000);
writeSjisStr(buf, base + 0x0004, base + 0x0018, profile.name);
buf.writeInt32LE(profile.lv, base + 0x0018);
buf.writeInt32LE(0, base + 0x0024); // Month points, TODO
buf.writeUInt32LE(accessTime, base + 0x0034);
encodeChara1(chara).copy(buf, base + 0x0044);
}
// Team Time Attack:
/*for (let i = 0; i < 6; i++) {
const base = 0x0344 + 0x20 * i;
buf.writeInt16LE(0x00001, base + 0x0000);
buf.writeInt8(0x02, base + 0x0003);
buf.writeInt32LE(0x00000003, base + 0x0004);
iconv.encode("str\0", "shift_jis").copy(buf, base + 0x0008);
buf.writeInt32LE(0x00000004, base + 0x001c);
}*/
return buf;
}
export function createAutoTeam1(res: CreateAutoTeamResponse): Buffer {
return _team1(0x007c, res);
}
export function loadTeam1(res: LoadTeamResponse): Buffer {
return _team1(0x0078, res);
}
export function createAutoTeam2(res: CreateAutoTeamResponse): Buffer {
return _team2(0x0078, res);
}
export function loadTeam2(res: LoadTeamResponse): Buffer {
return _team2(0x0074, res);
}
+10 -1
View File
@@ -1,6 +1,6 @@
import { CheckTeamNameResponse } from "../response/checkTeamName";
export function checkTeamName(res: CheckTeamNameResponse): Buffer {
export function checkTeamName1(res: CheckTeamNameResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x00a3, 0x0000);
@@ -8,3 +8,12 @@ export function checkTeamName(res: CheckTeamNameResponse): Buffer {
return buf;
}
export function checkTeamName2(res: CheckTeamNameResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x0098, 0x0000);
buf.writeUInt32LE(res.status, 0x0004);
return buf;
}
+20
View File
@@ -0,0 +1,20 @@
import { CreateProfileResponse } from "../response/createProfile";
export function createProfile1(res: CreateProfileResponse) {
const buf = Buffer.alloc(0x0020);
// Shares message type code with the "generic" response
buf.writeInt16LE(0x0001, 0x0000);
buf.writeInt32LE(res.aimeId, 0x0004);
return buf;
}
export function createProfile2(res: CreateProfileResponse) {
const buf = Buffer.alloc(0x0030);
buf.writeInt16LE(0x0001, 0x0000);
buf.writeInt32LE(res.aimeId, 0x0008);
return buf;
}
+11 -1
View File
@@ -1,6 +1,6 @@
import { CreateTeamResponse } from "../response/createTeam";
export function createTeam(res: CreateTeamResponse): Buffer {
export function createTeam1(res: CreateTeamResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x0072, 0x0000);
@@ -9,3 +9,13 @@ export function createTeam(res: CreateTeamResponse): Buffer {
return buf;
}
export function createTeam2(res: CreateTeamResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x006e, 0x0000);
buf.writeUInt32LE(res.status, 0x0004);
buf.writeUInt32LE(res.teamExtId, 0x0008);
return buf;
}
+10 -1
View File
@@ -1,6 +1,6 @@
import { DiscoverProfileResponse } from "../response/discoverProfile";
export function discoverProfile(res: DiscoverProfileResponse) {
export function discoverProfile1(res: DiscoverProfileResponse) {
const buf = Buffer.alloc(0x0010);
buf.writeInt16LE(0x006c, 0x0000);
@@ -8,3 +8,12 @@ export function discoverProfile(res: DiscoverProfileResponse) {
return buf;
}
export function discoverProfile2(res: DiscoverProfileResponse) {
const buf = Buffer.alloc(0x0010);
buf.writeInt16LE(0x0068, 0x0000);
buf.writeInt8(res.exists ? 1 : 0, 0x0004);
return buf;
}
+13 -1
View File
@@ -1,6 +1,6 @@
import { GenericResponse } from "../response/generic";
export function generic(res: GenericResponse) {
export function generic1(res: GenericResponse) {
const buf = Buffer.alloc(0x0020);
buf.writeInt16LE(0x0001, 0x0000);
@@ -8,3 +8,15 @@ export function generic(res: GenericResponse) {
return buf;
}
export function generic2(res: GenericResponse) {
const buf = Buffer.alloc(0x0030);
buf.writeInt16LE(0x0001, 0x0000);
buf.writeInt32LE(res.status || 0, 0x0004);
// write the AIME ID for createProfile in 2.12, kinda awkward but whatever.
buf.writeInt32LE(res.status || 0, 0x0008);
return buf;
}
+339 -67
View File
@@ -1,138 +1,169 @@
import logger from "debug";
import { Transform } from "stream";
import { _team } from "./_team";
import { checkTeamName } from "./checkTeamName";
import { createTeam } from "./createTeam";
import { discoverProfile } from "./discoverProfile";
import { generic } from "./generic";
import { lockProfile } from "./lockProfile";
import { lockProfileExtend } from "./lockProfileExtend";
import { load2on2 } from "./load2on2";
import { loadConfig } from "./loadConfig";
import { loadConfig2 } from "./loadConfig2";
import { loadEventInfo } from "./loadEventInfo";
import { loadGacha } from "./loadGacha";
import { loadGarage } from "./loadGarage";
import {
createAutoTeam1,
createAutoTeam2,
loadTeam1,
loadTeam2,
} from "./_team";
import { checkTeamName1, checkTeamName2 } from "./checkTeamName";
import { createProfile1, createProfile2 } from "./createProfile";
import { createTeam1, createTeam2 } from "./createTeam";
import { discoverProfile1, discoverProfile2 } from "./discoverProfile";
import { generic1, generic2 } from "./generic";
import { lockProfile1, lockProfile2 } from "./lockProfile";
import { lockProfileExtend1, lockProfileExtend2 } from "./lockProfileExtend";
import {
load2on2RankingPoints1,
load2on2Info1,
load2on2RankingPoints2,
load2on2Info2,
} from "./load2on2";
import {
loadConfigA_1,
loadConfigB_1,
loadConfigA_2,
loadConfigB_2,
} from "./loadConfig";
import { loadEventInfo1, loadEventInfo2 } from "./loadEventInfo";
import { loadGacha1, loadGacha2 } from "./loadGacha";
import { loadGarage1, loadGarage2 } from "./loadGarage";
import { loadGeneralReward } from "./loadGeneralReward";
import { loadGhost } from "./loadGhost";
import { loadProfile } from "./loadProfile";
import { loadRewardTable } from "./loadRewardTable";
import { loadServerList } from "./loadServerList";
import { loadStocker } from "./loadStocker";
import { loadTeamRanking } from "./loadTeamRanking";
import { loadTopTen } from "./loadTopTen";
import { saveExpedition } from "./saveExpedition";
import { saveGarage } from "./saveGarage";
import { saveNewCar } from "./saveNewCar";
import { saveTimeAttack } from "./saveTimeAttack";
import { saveTopic } from "./saveTopic";
import { unlockProfile } from "./unlockProfile";
import { updateProvisionalStoreRank } from "./updateProvisionalStoreRank";
import { updateStoryClearNum } from "./updateStoryClearNum";
import { updateTeamLeader } from "./updateTeamLeader";
import { updateTeamMember } from "./updateTeamMember";
import { loadGhost1, loadGhost2 } from "./loadGhost";
import { loadProfile2 } from "./loadProfile2";
import { loadProfile3 } from "./loadProfile3";
import { loadProfile4 } from "./loadProfile4";
import { loadRewardTable1, loadRewardTable2 } from "./loadRewardTable";
import { loadServerList1, loadServerList2 } from "./loadServerList";
import { loadStocker1, loadStocker2 } from "./loadStocker";
import { loadTeamRanking1, loadTeamRanking2 } from "./loadTeamRanking";
import { loadTopTen } from "./loadTopTen1";
import { loadTopTen2 } from "./loadTopTen2";
import { saveExpedition1, saveExpedition2 } from "./saveExpedition";
import { saveGarage1, saveGarage2 } from "./saveGarage";
import { saveNewCar1, saveNewCar2 } from "./saveNewCar";
import { saveTimeAttack1, saveTimeAttack2 } from "./saveTimeAttack";
import { saveTopic1, saveTopic2 } from "./saveTopic";
import { unlockProfile1, unlockProfile2 } from "./unlockProfile";
import {
updateProvisionalStoreRank1,
updateProvisionalStoreRank2,
} from "./updateProvisionalStoreRank";
import {
updateStoryClearNum1,
updateStoryClearNum2,
updateStoryClearNum3,
} from "./updateStoryClearNum";
import { updateTeamLeader1, updateTeamLeader2 } from "./updateTeamLeader";
import { updateTeamMember1, updateTeamMember2 } from "./updateTeamMember";
import { Response } from "../response";
import { ClientHello } from "../../common";
const debug = logger("app:idz:userdb:encoder");
function encode(res: Response): Buffer {
function encode110(res: Response): Buffer {
switch (res.type) {
case "check_team_name_res":
return checkTeamName(res);
return checkTeamName1(res);
case "create_auto_team_res":
return _team(res);
return createAutoTeam1(res);
case "create_profile_res":
return createProfile1(res);
case "create_team_res":
return createTeam(res);
return createTeam1(res);
case "discover_profile_res":
return discoverProfile(res);
return discoverProfile1(res);
case "generic_res":
return generic(res);
return generic1(res);
case "load_2on2_res":
return load2on2(res);
case "load_2on2_ranking_points_res":
return load2on2RankingPoints1(res);
case "load_config_res":
return loadConfig(res);
case "load_2on2_info_res":
return load2on2Info1(res);
case "load_config_v2_res":
return loadConfig2(res);
case "load_config_A_res":
return loadConfigA_1(res);
case "load_config_B_res":
return loadConfigB_1(res);
case "load_event_info_res":
return loadEventInfo(res);
return loadEventInfo1(res);
case "load_gacha_res":
return loadGacha(res);
return loadGacha1(res);
case "load_garage_res":
return loadGarage(res);
return loadGarage1(res);
case "load_general_reward_res":
return loadGeneralReward(res);
case "load_ghost_res":
return loadGhost(res);
return loadGhost1(res);
case "load_profile_res":
return loadProfile(res);
return loadProfile2(res);
case "load_reward_table_res":
return loadRewardTable(res);
return loadRewardTable1(res);
case "load_server_list_res":
return loadServerList(res);
return loadServerList1(res);
case "load_stocker_res":
return loadStocker(res);
return loadStocker1(res);
case "load_team_res":
return _team(res);
return loadTeam1(res);
case "load_team_ranking_res":
return loadTeamRanking(res);
return loadTeamRanking1(res);
case "load_top_ten_res":
return loadTopTen(res);
case "lock_profile_extend_res":
return lockProfileExtend(res);
return lockProfileExtend1(res);
case "lock_profile_res":
return lockProfile(res);
return lockProfile1(res);
case "save_expedition_res":
return saveExpedition(res);
return saveExpedition1(res);
case "save_garage_res":
return saveGarage(res);
return saveGarage1(res);
case "save_new_car_res":
return saveNewCar(res);
return saveNewCar1(res);
case "save_time_attack_res":
return saveTimeAttack(res);
return saveTimeAttack1(res);
case "unlock_profile_res":
return unlockProfile(res);
return unlockProfile1(res);
case "update_provisional_store_rank_res":
return updateProvisionalStoreRank(res);
return updateProvisionalStoreRank1(res);
case "update_story_clear_num_res":
return updateStoryClearNum(res);
return updateStoryClearNum1(res);
case "update_team_leader_res":
return updateTeamLeader(res);
return updateTeamLeader1(res);
case "update_team_member_res":
return updateTeamMember(res);
return updateTeamMember1(res);
case "save_topic_res":
return saveTopic(res);
return saveTopic1(res);
default:
const exhaustCheck: never = res;
@@ -141,10 +172,251 @@ function encode(res: Response): Buffer {
}
}
export default function writeResponse(res: Response) {
function encode130(res: Response): Buffer {
switch (res.type) {
case "check_team_name_res":
return checkTeamName1(res);
case "create_auto_team_res":
return createAutoTeam1(res);
case "create_profile_res":
return createProfile1(res);
case "create_team_res":
return createTeam1(res);
case "discover_profile_res":
return discoverProfile1(res);
case "generic_res":
return generic1(res);
case "load_2on2_ranking_points_res":
return load2on2RankingPoints1(res);
case "load_2on2_info_res":
return load2on2Info1(res);
case "load_config_A_res":
return loadConfigA_1(res);
case "load_config_B_res":
return loadConfigB_1(res);
case "load_event_info_res":
return loadEventInfo1(res);
case "load_gacha_res":
return loadGacha1(res);
case "load_garage_res":
return loadGarage1(res);
case "load_general_reward_res":
return loadGeneralReward(res);
case "load_ghost_res":
return loadGhost1(res);
case "load_profile_res":
return loadProfile3(res);
case "load_reward_table_res":
return loadRewardTable1(res);
case "load_server_list_res":
return loadServerList1(res);
case "load_stocker_res":
return loadStocker1(res);
case "load_team_res":
return loadTeam1(res);
case "load_team_ranking_res":
return loadTeamRanking1(res);
case "load_top_ten_res":
return loadTopTen(res);
case "lock_profile_extend_res":
return lockProfileExtend1(res);
case "lock_profile_res":
return lockProfile1(res);
case "save_expedition_res":
return saveExpedition1(res);
case "save_garage_res":
return saveGarage1(res);
case "save_new_car_res":
return saveNewCar1(res);
case "save_time_attack_res":
return saveTimeAttack1(res);
case "unlock_profile_res":
return unlockProfile1(res);
case "update_provisional_store_rank_res":
return updateProvisionalStoreRank1(res);
case "update_story_clear_num_res":
return updateStoryClearNum2(res);
case "update_team_leader_res":
return updateTeamLeader1(res);
case "update_team_member_res":
return updateTeamMember1(res);
case "save_topic_res":
return saveTopic1(res);
default:
const exhaustCheck: never = res;
throw new Error(`No writer fn for ${res["type"]}`);
}
}
function encode210(res: Response): Buffer {
switch (res.type) {
case "check_team_name_res":
return checkTeamName2(res);
case "create_auto_team_res":
return createAutoTeam2(res);
case "create_profile_res":
return createProfile2(res);
case "create_team_res":
return createTeam2(res);
case "discover_profile_res":
return discoverProfile2(res);
case "generic_res":
return generic2(res);
case "load_2on2_ranking_points_res":
return load2on2RankingPoints2(res);
case "load_2on2_info_res":
return load2on2Info2(res);
case "load_config_A_res":
return loadConfigA_2(res);
case "load_config_B_res":
return loadConfigB_2(res);
case "load_event_info_res":
return loadEventInfo2(res);
case "load_gacha_res":
return loadGacha2(res);
case "load_garage_res":
return loadGarage2(res);
case "load_general_reward_res":
return loadGeneralReward(res);
case "load_ghost_res":
return loadGhost2(res);
case "load_profile_res":
return loadProfile4(res);
case "load_reward_table_res":
return loadRewardTable2(res);
case "load_server_list_res":
return loadServerList2(res);
case "load_stocker_res":
return loadStocker2(res);
case "load_team_res":
return loadTeam2(res);
case "load_team_ranking_res":
return loadTeamRanking2(res);
case "load_top_ten_res":
return loadTopTen2(res);
case "lock_profile_extend_res":
return lockProfileExtend2(res);
case "lock_profile_res":
return lockProfile2(res);
case "save_expedition_res":
return saveExpedition2(res);
case "save_garage_res":
return saveGarage2(res);
case "save_new_car_res":
return saveNewCar2(res);
case "save_time_attack_res":
return saveTimeAttack2(res);
case "unlock_profile_res":
return unlockProfile2(res);
case "update_provisional_store_rank_res":
return updateProvisionalStoreRank2(res);
case "update_story_clear_num_res":
return updateStoryClearNum3(res);
case "update_team_leader_res":
return updateTeamLeader2(res);
case "update_team_member_res":
return updateTeamMember2(res);
case "save_topic_res":
return saveTopic2(res);
default:
const exhaustCheck: never = res;
throw new Error(`No writer fn for ${res["type"]}`);
}
}
function encode(res: Response, clientHello: ClientHello) {
switch (clientHello.protocol) {
case "110":
return encode110(res);
case "130":
return encode130(res);
case "210":
return encode210(res);
default:
throw new Error(`Unsupported protocol version ${clientHello.protocol}`);
}
}
export default function writeResponse(
res: Response,
clientHello: ClientHello
) {
debug("Object: %j", res);
const buf = encode(res);
const buf = encode(res, clientHello);
if (debug.enabled) {
debug("Encoded: %s", buf.toString("hex"));
+24 -21
View File
@@ -1,25 +1,11 @@
import {
Load2on2Response,
Load2on2Response1,
Load2on2Response2,
Load2on2InfoResponse,
Load2on2RankingPointsResponse,
} from "../response/load2on2";
export function load2on2(res: Load2on2Response): Buffer {
switch (res.format) {
case 1:
return load2on2_v1(res);
case 2:
return load2on2_v2(res);
default:
const exhaust: never = res;
throw new Error(`Unsupported 2on2 response format ${res["format"]}`);
}
}
function load2on2_v1(res: Load2on2Response1): Buffer {
export function load2on2RankingPoints1(
res: Load2on2RankingPointsResponse
): Buffer {
const buf = Buffer.alloc(0x04c0);
buf.writeInt16LE(0x00b1, 0x0000);
@@ -27,11 +13,28 @@ function load2on2_v1(res: Load2on2Response1): Buffer {
return buf;
}
// Same size but presumably incompatible somehow
function load2on2_v2(res: Load2on2Response2): Buffer {
export function load2on2Info1(res: Load2on2InfoResponse): Buffer {
const buf = Buffer.alloc(0x04c0);
buf.writeInt16LE(0x0133, 0x0000);
return buf;
}
export function load2on2RankingPoints2(
res: Load2on2RankingPointsResponse
): Buffer {
const buf = Buffer.alloc(0x1290);
buf.writeInt16LE(0x00a4, 0x0000);
return buf;
}
export function load2on2Info2(res: Load2on2InfoResponse): Buffer {
const buf = Buffer.alloc(0x0540);
buf.writeInt16LE(0x0133, 0x0000);
return buf;
}
+33 -2
View File
@@ -1,6 +1,9 @@
import { LoadConfigResponse } from "../response/loadConfig";
import {
LoadConfigResponseA,
LoadConfigResponseB,
} from "../response/loadConfig";
export function loadConfig(res: LoadConfigResponse) {
export function loadConfigA_1(res: LoadConfigResponseA) {
const buf = Buffer.alloc(0x01a0);
buf.writeInt16LE(0x0005, 0x0000);
@@ -9,3 +12,31 @@ export function loadConfig(res: LoadConfigResponse) {
return buf;
}
export function loadConfigB_1(res: LoadConfigResponseB) {
const buf = Buffer.alloc(0x0230);
buf.writeInt16LE(0x00ac, 0x0000);
buf.writeInt8(res.status, 0x0002);
return buf;
}
export function loadConfigA_2(res: LoadConfigResponseA) {
const buf = Buffer.alloc(0x05e0);
buf.writeInt16LE(0x0005, 0x0000);
buf.writeInt8(res.status, 0x0002);
buf.writeUInt16LE(res.serverVersion, 0x0016);
return buf;
}
export function loadConfigB_2(res: LoadConfigResponseB) {
const buf = Buffer.alloc(0x0240);
buf.writeInt16LE(0x00a1, 0x0000);
buf.writeInt8(res.status, 0x0002);
return buf;
}
-10
View File
@@ -1,10 +0,0 @@
import { LoadConfigResponse2 } from "../response/loadConfig2";
export function loadConfig2(res: LoadConfigResponse2) {
const buf = Buffer.alloc(0x0230);
buf.writeInt16LE(0x00ac, 0x0000);
buf.writeInt8(res.status, 0x0002);
return buf;
}
+9 -1
View File
@@ -1,9 +1,17 @@
import { LoadEventInfoResponse } from "../response/loadEventInfo";
export function loadEventInfo(res: LoadEventInfoResponse): Buffer {
export function loadEventInfo1(res: LoadEventInfoResponse): Buffer {
const buf = Buffer.alloc(0x01b0);
buf.writeUInt16LE(0x00bf, 0x0000);
return buf;
}
export function loadEventInfo2(res: LoadEventInfoResponse): Buffer {
const buf = Buffer.alloc(0x01b0);
buf.writeUInt16LE(0x00ad, 0x0000);
return buf;
}
+10 -1
View File
@@ -1,6 +1,6 @@
import { LoadGachaResponse } from "../response/loadGacha";
export function loadGacha(res: LoadGachaResponse): Buffer {
export function loadGacha1(res: LoadGachaResponse): Buffer {
const buf = Buffer.alloc(0x0090);
buf.writeUInt16LE(0x00c2, 0x0000);
@@ -8,3 +8,12 @@ export function loadGacha(res: LoadGachaResponse): Buffer {
return buf;
}
export function loadGacha2(res: LoadGachaResponse): Buffer {
const buf = Buffer.alloc(0x0090);
buf.writeUInt16LE(0x00b0, 0x0000);
buf.writeUInt8(res.awardedToday ? 0x01 : 0x00, 0x0004);
return buf;
}
+14 -1
View File
@@ -1,7 +1,7 @@
import { encodeCar } from "./_car";
import { LoadGarageResponse } from "../response/loadGarage";
export function loadGarage(res: LoadGarageResponse): Buffer {
export function loadGarage1(res: LoadGarageResponse): Buffer {
const buf = Buffer.alloc(0x03d0);
buf.writeUInt16LE(0x0091, 0x0000);
@@ -13,3 +13,16 @@ export function loadGarage(res: LoadGarageResponse): Buffer {
return buf;
}
export function loadGarage2(res: LoadGarageResponse): Buffer {
const buf = Buffer.alloc(0x0420);
buf.writeUInt16LE(0x0088, 0x0000);
buf.writeUInt16LE(res.cars.length, 0x0004);
for (let i = 0; i < res.cars.length; i++) {
encodeCar(res.cars[i]).copy(buf, 0x0008 + 0x0068 * i);
}
return buf;
}
+2 -3
View File
@@ -1,6 +1,5 @@
import iconv from "iconv-lite";
import { LoadGeneralRewardResponse } from "../response/loadGeneralReward";
import { writeSjisStr } from "../../util/bin";
export function loadGeneralReward(res: LoadGeneralRewardResponse) {
const buf = Buffer.alloc(0x0330);
@@ -15,7 +14,7 @@ export function loadGeneralReward(res: LoadGeneralRewardResponse) {
const item = res.items[i];
const base = 0x04 + 0x50 * i;
iconv.encode(item.field_04, "shift_jis").copy(buf, base + 0x04);
writeSjisStr(buf, base + 0x04, base + 0x2c, item.field_04);
buf.writeUInt32LE(item.field_2C, base + 0x2c);
buf.writeUInt8(item.field_38, base + 0x38);
buf.writeUInt8(item.field_39, base + 0x39);
+10 -2
View File
@@ -1,11 +1,11 @@
import { LoadGhostResponse } from "../response/loadGhost";
export function loadGhost(res: LoadGhostResponse): Buffer {
function _loadGhost(msgCode: number, res: LoadGhostResponse): Buffer {
const buf = Buffer.alloc(0x0070);
// No idea what any of this even does.
buf.writeUInt16LE(0x00a1, 0x0000);
buf.writeUInt16LE(msgCode, 0x0000);
buf.writeUInt16LE(0x0005, 0x0002); // Chunk presence flags: 4 | 1
for (let i = 0; i < 2; i++) {
@@ -29,3 +29,11 @@ export function loadGhost(res: LoadGhostResponse): Buffer {
return buf;
}
export function loadGhost1(res: LoadGhostResponse): Buffer {
return _loadGhost(0x00a1, res);
}
export function loadGhost2(res: LoadGhostResponse): Buffer {
return _loadGhost(0x0096, res);
}
-22
View File
@@ -1,22 +0,0 @@
import { loadProfile1 } from "./loadProfile1";
import { loadProfile2 } from "./loadProfile2";
import { loadProfile3 } from "./loadProfile3";
import { LoadProfileResponse } from "../response/loadProfile";
export function loadProfile(res: LoadProfileResponse) {
switch (res.format) {
case 1:
return loadProfile1(res);
case 2:
return loadProfile2(res);
case 3:
return loadProfile3(res);
default:
const exhaust: never = res;
throw new Error(`Unsupported profile response format ${res["format"]}`);
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
import { LoadProfileResponse1 } from "../response/loadProfile";
import { LoadProfileResponse } from "../response/loadProfile";
// Sending this causes an error in v1.21, so it is currently unmapped and
// unimplemented.
export function loadProfile1(res: LoadProfileResponse1) {
export function loadProfile1(res: LoadProfileResponse) {
const buf = Buffer.alloc(0x0c60);
buf.writeInt16LE(0x0064, 0x0000);
+18 -11
View File
@@ -1,12 +1,11 @@
import iconv from "iconv-lite";
import { encodeBitmap } from "./_bitmap";
import { encodeCar } from "./_car";
import { encodeChara } from "./_chara";
import { encodeChara1 } from "./_chara";
import { encodeMission } from "./_mission";
import { LoadProfileResponse2 } from "../response/loadProfile";
import { LoadProfileResponse } from "../response/loadProfile";
import { writeSjisStr } from "../../util/bin";
export function loadProfile2(res: LoadProfileResponse2) {
export function loadProfile2(res: LoadProfileResponse) {
const buf = Buffer.alloc(0x0d30);
// FLAMETHROWER ANALYSIS (watch out for C strings)
@@ -38,14 +37,22 @@ export function loadProfile2(res: LoadProfileResponse2) {
}
}
for (let i = 0; i < 9 && i < res.story.rows.length; i++) {
const row = res.story.rows[i];
for (let i = 0; i < 9; i++) {
const row = res.story.rows.get(i);
const rowOffset = 0x0228 + i * 0x26;
for (let j = 0; j < 9 && j < row.cells.length; j++) {
const cell = row.cells[j];
if (row === undefined) {
continue;
}
for (let j = 0; j < 9; j++) {
const cell = row.cells.get(j);
const cellOffset = rowOffset + j * 4;
if (cell === undefined) {
continue;
}
buf.writeUInt16LE(cell.a, cellOffset + 0);
buf.writeUInt16LE(cell.b, cellOffset + 2);
}
@@ -87,11 +94,11 @@ export function loadProfile2(res: LoadProfileResponse2) {
buf.writeUInt32LE(res.settings.pack, 0x03d8);
buf.writeUInt32LE(res.dpoint, 0x03e8);
buf.writeUInt32LE(res.fame, 0x0404);
iconv.encode(res.name + "\0", "shift_jis").copy(buf, 0x03ee);
writeSjisStr(buf, 0x03ee, 0x40e, res.name);
buf.writeUInt8(res.story.y, 0x0670);
buf.writeUInt16LE(res.story.x, 0x06bc);
encodeMission(res.missions.solo).copy(buf, 0x06e4);
encodeChara(res.chara).copy(buf, 0x070c);
encodeChara1(res.chara).copy(buf, 0x070c);
encodeBitmap(res.titles, 0xb4).copy(buf, 0x720);
buf.writeUInt8(res.settings.aura, 0x07d6);
buf.writeUInt8(res.settings.paperCup, 0x07d9);
+18 -11
View File
@@ -1,12 +1,11 @@
import iconv from "iconv-lite";
import { encodeBitmap } from "./_bitmap";
import { encodeCar } from "./_car";
import { encodeChara } from "./_chara";
import { encodeChara1 } from "./_chara";
import { encodeMission } from "./_mission";
import { LoadProfileResponse3 } from "../response/loadProfile";
import { LoadProfileResponse } from "../response/loadProfile";
import { writeSjisStr } from "../../util/bin";
export function loadProfile3(res: LoadProfileResponse3) {
export function loadProfile3(res: LoadProfileResponse) {
const buf = Buffer.alloc(0x0ea0);
// Initialize all TA grades to uhh... fuck knows
@@ -38,14 +37,22 @@ export function loadProfile3(res: LoadProfileResponse3) {
// space for story cells, and 27 rows * 19 bytes per row = 513 bytes, which
// is the max that will fit (the final byte of each row is unused).
for (let i = 0; i < 27 && i < res.story.rows.length; i++) {
const row = res.story.rows[i];
for (let i = 0; i < 27; i++) {
const row = res.story.rows.get(i);
const rowOffset = 0x0256 + i * 0x13;
for (let j = 0; j < 9 && j < row.cells.length; j++) {
const cell = row.cells[j];
if (row === undefined) {
continue;
}
for (let j = 0; j < 9; j++) {
const cell = row.cells.get(j);
const cellOffset = rowOffset + j * 2;
if (cell === undefined) {
continue;
}
buf.writeUInt8(cell.a, cellOffset + 0);
buf.writeUInt8(cell.b, cellOffset + 1);
}
@@ -87,11 +94,11 @@ export function loadProfile3(res: LoadProfileResponse3) {
buf.writeUInt32LE(res.settings.pack, 0x04b4);
buf.writeUInt32LE(res.dpoint, 0x04c4);
buf.writeUInt32LE(res.fame, 0x04e0);
iconv.encode(res.name + "\0", "shift_jis").copy(buf, 0x04ca);
writeSjisStr(buf, 0x04ca, 0x04ea, res.name);
buf.writeUInt8(res.story.y, 0x080c);
buf.writeUInt16LE(res.story.x, 0x0828);
encodeMission(res.missions.solo).copy(buf, 0x0858);
encodeChara(res.chara).copy(buf, 0x0880);
encodeChara1(res.chara).copy(buf, 0x0880);
encodeBitmap(res.titles, 0xb4).copy(buf, 0x0894);
buf.writeUInt8(res.settings.aura, 0x094a);
buf.writeUInt8(res.settings.paperCup, 0x094d);
+137
View File
@@ -0,0 +1,137 @@
import { encodeBitmap } from "./_bitmap";
import { encodeCar } from "./_car";
import { encodeChara2 } from "./_chara";
import { encodeMission } from "./_mission";
import { LoadProfileResponse } from "../response/loadProfile";
import { writeSjisStr } from "../../util/bin";
export function loadProfile4(res: LoadProfileResponse) {
const buf = Buffer.alloc(0x1360);
buf.writeUInt16LE(0x012e, 0x0000);
buf.writeUInt16LE(res.lv, 0x06f0);
buf.writeUInt32LE(res.exp, 0x06f4);
buf.writeUInt32LE(res.dpoint, 0x070c);
buf.writeUInt32LE(res.fame, 0x0728);
buf.writeUInt32LE(res.aimeId, 0x06dc);
buf.writeUInt32LE(res.teamId || 0xffffffff, 0x0c60);
buf.writeUInt32LE(res.mileage, 0x06e0);
encodeMission(res.missions.solo).copy(buf, 0x0aa0);
encodeChara2(res.chara).copy(buf, 0x0ac8);
encodeBitmap(res.titles, 0xb4).copy(buf, 0x0ade);
buf.writeUInt32LE(res.settings.pack, 0x06fc);
buf.writeUInt8(res.settings.aura, 0x0c57);
buf.writeUInt8(res.settings.paperCup, 0x0c5a);
buf.writeUInt8(res.settings.gauges, 0x0c5b);
buf.writeUInt8(res.settings.drivingStyle, 0x1184);
buf.writeUInt16LE(res.settings.music, 0x06ec);
writeSjisStr(buf, 0x0712, 0x0732, res.name);
encodeCar(res.car).copy(buf, 0x1290);
buf.writeUInt32LE(res.carCount, 0x1288);
buf.writeUInt16LE(res.unlocks.auras, 0x00b0);
buf.writeUInt8(res.unlocks.cup, 0x00b4);
buf.writeUInt32LE(res.unlocks.gauges, 0x00b8);
buf.writeUInt32LE(res.unlocks.lastMileageReward, 0x0218);
buf.writeUInt16LE(res.unlocks.music, 0x021c);
buf.writeUInt16LE(res.teamLeader ? 1 : 0, 0x06a0);
// Weekly Missions
buf.writeUInt32LE(
(new Date(res.weeklyMissions.weeklyReset).getTime() / 1000) | 0,
0x1068
);
buf.writeUInt16LE(res.weeklyMissions.weeklyMissionLeft, 0x105a);
buf.writeUInt16LE(res.weeklyMissions.weeklyProgressLeft, 0x105c);
buf.writeUInt16LE(res.weeklyMissions.weeklyParamsLeft, 0x105e);
buf.writeUInt16LE(res.weeklyMissions.weeklyMissionRight, 0x1060);
buf.writeUInt16LE(res.weeklyMissions.weeklyProgressRight, 0x1062);
buf.writeUInt16LE(res.weeklyMissions.weeklyParamsRight, 0x1064);
// send twice, once as new stamps, once as old stamps,
// because we don't keep track of the "new!" state.
encodeBitmap(res.stamps, 0x26).copy(buf, 0x10f8);
encodeBitmap(res.stamps, 0x26).copy(buf, 0x111e);
// Selected Stamps
const selectedStamps = res.selectedStamps;
buf.writeUInt16LE(selectedStamps.stamp01, 0x1144);
buf.writeUInt16LE(selectedStamps.stamp02, 0x1146);
buf.writeUInt16LE(selectedStamps.stamp03, 0x1148);
buf.writeUInt16LE(selectedStamps.stamp04, 0x114a);
const { freeCar, freeContinue } = res.tickets;
if (freeCar) {
buf.writeUInt32LE((freeCar.validFrom.getTime() / 1000) | 0, 0x0214);
}
if (freeContinue) {
buf.writeUInt32LE((freeContinue.validFrom.getTime() / 1000) | 0, 0x0700);
buf.writeUInt32LE((freeContinue.validTo.getTime() / 1000) | 0, 0x0704);
}
// Course plays
for (const [courseId, playCount] of res.coursePlays.entries()) {
if (courseId < 0 || courseId >= 20) {
throw new Error(`Course id out of range: ${courseId}`);
}
buf.writeUInt16LE(playCount, 0x0784 + 2 * courseId);
}
for (const score of res.timeAttack) {
const { routeNo } = score;
buf.writeUInt32LE(
(new Date(score.timestamp).getTime() / 1000) | 0, // Date ctor hack
0x00e4 + routeNo * 4
);
buf.writeUInt16LE(0xffff, 0x0184 + 2 * routeNo); // National rank
buf.writeUInt32LE((score.totalTime * 1000) | 0, 0x0824 + 4 * routeNo); // Total Time in MSEC // DONE
buf.writeUInt8(score.flags, 0x0914 + routeNo); // FLAGS
buf.writeUInt8(score.grade, 0x0a2c + routeNo); // MEDALS
for (let i = 0; i < 3; i++) {
buf.writeUInt16LE(
(score.sectionTimes[i] * 1000) >> 2,
0x093c + 6 * routeNo + 2 * i // SECTION TIMES DONE
);
}
}
// the format has changed quite a bit in v2
// seems like it's just split into the big chapters now
// with more cells of course, enough space for 30ish
// the biggest chapter is chapter 1 with a total of 22 races
// but for "security" let's go with up to 28
// first two bytes at the start seem to declare the chapter number
for (let i = 0; i < 6; i++) {
const row = res.story.rows.get(i);
const rowOffset = 0x0258 + i * 0x7a;
if (row === undefined) {
continue;
}
for (let j = 0; j < 28; j++) {
const cell = row.cells.get(j);
// Four bytes per column
const cellOffset = rowOffset + j * 4;
if (cell === undefined) {
continue;
}
buf.writeUInt8(cell.a, cellOffset + 0);
buf.writeUInt8(cell.b, cellOffset + 1);
buf.writeUInt16LE(cell.c, cellOffset + 2);
}
}
buf.writeUInt8(res.story.y, 0x0a54);
buf.writeUInt16LE(res.story.x, 0x0a70);
return buf;
}
+9 -1
View File
@@ -1,9 +1,17 @@
import { LoadRewardTableResponse } from "../response/loadRewardTable";
export function loadRewardTable(res: LoadRewardTableResponse) {
export function loadRewardTable1(res: LoadRewardTableResponse) {
const buf = Buffer.alloc(0x01c0);
buf.writeInt16LE(0x0087, 0x0000);
return buf;
}
export function loadRewardTable2(res: LoadRewardTableResponse) {
const buf = Buffer.alloc(0x01c0);
buf.writeInt16LE(0x0080, 0x0000);
return buf;
}
+31 -1
View File
@@ -1,6 +1,6 @@
import { LoadServerListResponse } from "../response/loadServerList";
export function loadServerList(res: LoadServerListResponse) {
export function loadServerList1(res: LoadServerListResponse) {
const buf = Buffer.alloc(0x04b0);
buf.writeInt16LE(0x0007, 0x0000);
@@ -29,3 +29,33 @@ export function loadServerList(res: LoadServerListResponse) {
return buf;
}
export function loadServerList2(res: LoadServerListResponse) {
const buf = Buffer.alloc(0x04b0);
buf.writeInt16LE(0x0007, 0x0000);
buf.writeInt16LE(res.status, 0x0004);
buf.write(res.userDb.addr, 0x0006);
buf.writeInt16LE(res.userDb.tcp, 0x0086);
buf.writeInt16LE(res.userDb.http, 0x0088);
buf.write(res.matchAddr, 0x008a);
buf.writeInt16LE(res.matchPort.tcp, 0x010a);
buf.writeInt16LE(res.matchPort.udpSend, 0x010c);
buf.writeInt16LE(res.matchPort.udpRecv, 0x010e);
buf.writeInt16LE(res.tagMatchPort.tcp, 0x0110);
buf.writeInt16LE(res.tagMatchPort.udpSend, 0x0112);
buf.writeInt16LE(res.tagMatchPort.udpRecv, 0x0114);
buf.write(res.event.addr, 0x0116);
buf.writeInt16LE(res.event.tcp, 0x0196);
buf.write(res.screenshot.addr, 0x019a);
buf.writeInt16LE(res.screenshot.tcp, 0x021a);
buf.write(res.pingReturn, 0x021e);
buf.write(res.echo1.addr, 0x029e);
buf.write(res.echo2.addr, 0x031e);
buf.writeInt16LE(res.echo1.udp, 0x39e);
buf.writeInt16LE(res.echo2.udp, 0x3a0);
buf.write(res.newsUrl, 0x03a2);
buf.write(res.reportErrorUrl, 0x0426);
return buf;
}
+12 -1
View File
@@ -1,7 +1,7 @@
import { encodeBitmap } from "./_bitmap";
import { LoadStockerResponse } from "../response/loadStocker";
export function loadStocker(res: LoadStockerResponse) {
export function loadStocker1(res: LoadStockerResponse) {
const buf = Buffer.alloc(0x00a0);
buf.writeInt16LE(0x00a8, 0x0000);
@@ -10,3 +10,14 @@ export function loadStocker(res: LoadStockerResponse) {
return buf;
}
export function loadStocker2(res: LoadStockerResponse) {
const buf = Buffer.alloc(0x00a0);
buf.writeInt16LE(0x009d, 0x0000);
buf.writeUInt8(res.status, 0x0004);
encodeBitmap(res.backgrounds, 0x2c).copy(buf, 0x0005);
encodeBitmap(res.myChara, 0x98).copy(buf, 0x0031);
return buf;
}
+9 -1
View File
@@ -1,6 +1,6 @@
import { LoadTeamRankingResponse } from "../response/loadTeamRanking";
export function loadTeamRanking(res: LoadTeamRankingResponse): Buffer {
export function loadTeamRanking1(res: LoadTeamRankingResponse): Buffer {
const buf = Buffer.alloc(0x0ba0);
buf.writeUInt16LE(0x00ba, 0x0000);
@@ -8,3 +8,11 @@ export function loadTeamRanking(res: LoadTeamRankingResponse): Buffer {
return buf;
}
export function loadTeamRanking2(res: LoadTeamRankingResponse): Buffer {
const buf = Buffer.alloc(0x0ba0);
buf.writeUInt16LE(0x00a8, 0x0000);
return buf;
}
@@ -1,5 +1,4 @@
import iconv from "iconv-lite";
import { writeSjisStr } from "../../util/bin";
import { LoadTopTenResponse } from "../response/loadTopTen";
export function loadTopTen(res: LoadTopTenResponse): Buffer {
@@ -49,9 +48,9 @@ export function loadTopTen(res: LoadTopTenResponse): Buffer {
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.team.name, "shift_jis").copy(buf, innerOff + 0x0028);
iconv.encode(row.shopName, "shift_jis").copy(buf, innerOff + 0x0048);
writeSjisStr(buf, innerOff + 0x0014, innerOff + 0x0028, row.driverName);
writeSjisStr(buf, innerOff + 0x0028, innerOff + 0x0048, row.team.name);
writeSjisStr(buf, innerOff + 0x0048, innerOff + 0x0074, row.shopName);
buf.writeUInt32LE(row.team.nameBg, innerOff + 0x0074);
buf.writeUInt16LE(row.team.nameFx, innerOff + 0x0078);
buf.writeUInt8(row.field_7C, innerOff + 0x007c);
@@ -70,7 +69,7 @@ export function loadTopTen(res: LoadTopTenResponse): Buffer {
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);
writeSjisStr(buf, offset + 0x08, offset + 0x1c, trailer.name);
} else {
buf.writeUInt8(0xff, offset + 0x02);
}
+79
View File
@@ -0,0 +1,79 @@
import { writeSjisStr } from "../../util/bin";
import { LoadTopTenResponse } from "../response/loadTopTen";
export function loadTopTen2(res: LoadTopTenResponse): Buffer {
const buf = Buffer.alloc(0x1720);
buf.writeUInt16LE(0x00ce, 0x0000);
buf.writeUInt16LE(res.courseCount, 0x0004);
for (let i = 0; i < 3; i++) {
if (i >= res.courses.length) {
break;
}
const course = res.courses[i];
const outerOff = 0x0006 + 0x794 * i;
buf.writeUInt16LE(course.routeNo << 1, outerOff + 0x0002);
buf.writeUInt16LE(course.field_02, outerOff + 0x0004); // Bitmask-y?
if (course.rows.length > 0) {
// Section times for the top result (and only top result) are sent OOB
const best = course.rows[0];
for (let j = 0; j < 3; j++) {
buf.writeUInt32LE(
(best.ta.sectionTimes[j] * 1000) | 0,
outerOff + 0x0004 + 4 * j
);
}
}
for (let j = 0; j < 10; j++) {
if (j >= course.rows.length) {
break;
}
const row = course.rows[j];
const innerOff = outerOff + 0x0012 + 0xc0 * j;
buf.writeUInt32LE(row.field_00, innerOff + 0x0000);
buf.writeUInt32LE((row.ta.totalTime * 1000) | 0, innerOff + 0x0004);
buf.writeUInt32LE(
(row.ta.timestamp.getTime() / 1000) | 0,
innerOff + 0x0008
);
buf.writeUInt16LE(row.ta.carSelector, 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);
writeSjisStr(buf, innerOff + 0x0014, innerOff + 0x0028, row.driverName);
writeSjisStr(buf, innerOff + 0x0028, innerOff + 0x0048, row.team.name);
writeSjisStr(buf, innerOff + 0x0048, innerOff + 0x0074, row.shopName);
buf.writeUInt32LE(row.team.nameBg, innerOff + 0x0074);
buf.writeUInt16LE(row.team.nameFx, innerOff + 0x0078);
buf.writeUInt8(row.field_7C, innerOff + 0x007c);
buf.writeUInt8(row.field_7D, innerOff + 0x007d);
// 66 bytes of empty space at the end. Maybe a string used to live here?
}
}
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);
writeSjisStr(buf, offset + 0x08, offset + 0x1c, trailer.name);
} else {
buf.writeUInt8(0xff, offset + 0x02);
}
}
return buf;
}
+12 -1
View File
@@ -6,7 +6,7 @@ const statusCodes = {
obsolete: 0x0002,
};
export function lockProfile(res: LockProfileResponse) {
export function lockProfile1(res: LockProfileResponse) {
const buf = Buffer.alloc(0x0020);
buf.writeInt16LE(0x006a, 0x0000);
@@ -16,3 +16,14 @@ export function lockProfile(res: LockProfileResponse) {
return buf;
}
export function lockProfile2(res: LockProfileResponse) {
const buf = Buffer.alloc(0x0020);
buf.writeInt16LE(0x0066, 0x0000);
buf.writeInt8(statusCodes[res.status], 0x0018);
buf.writeInt16LE(res.field_001A, 0x001a);
buf.writeInt32LE(res.lockExpiry.getTime() / 1000, 0x001c);
return buf;
}
+10 -1
View File
@@ -1,6 +1,6 @@
import { LockProfileExtendResponse } from "../response/lockProfileExtend";
export function lockProfileExtend(res: LockProfileExtendResponse): Buffer {
export function lockProfileExtend1(res: LockProfileExtendResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x006e, 0x0000);
@@ -8,3 +8,12 @@ export function lockProfileExtend(res: LockProfileExtendResponse): Buffer {
return buf;
}
export function lockProfileExtend2(res: LockProfileExtendResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x006a, 0x0000);
buf.writeUInt8(res.status, 0x0004);
return buf;
}
+3 -22
View File
@@ -1,25 +1,6 @@
import {
SaveExpeditionResponse,
SaveExpeditionResponse1,
SaveExpeditionResponse2,
} from "../response/saveExpedition";
import { SaveExpeditionResponse } from "../response/saveExpedition";
export function saveExpedition(res: SaveExpeditionResponse): Buffer {
switch (res.format) {
case 1:
return saveExpedition1(res);
case 2:
return saveExpedition2(res);
default:
const exhaust: never = res;
throw new Error(`Unsupported data format ${res["format"]}`);
}
}
function saveExpedition1(res: SaveExpeditionResponse1): Buffer {
export function saveExpedition1(res: SaveExpeditionResponse): Buffer {
// in awe of the size of this lad
const buf = Buffer.alloc(0x17c0);
@@ -28,7 +9,7 @@ function saveExpedition1(res: SaveExpeditionResponse1): Buffer {
return buf;
}
function saveExpedition2(res: SaveExpeditionResponse2): Buffer {
export function saveExpedition2(res: SaveExpeditionResponse): Buffer {
// absolute unit
const buf = Buffer.alloc(0x18ac);
+10 -1
View File
@@ -1,6 +1,6 @@
import { SaveGarageResponse } from "../response/saveGarage";
export function saveGarage(res: SaveGarageResponse): Buffer {
export function saveGarage1(res: SaveGarageResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x008f, 0x0000);
@@ -8,3 +8,12 @@ export function saveGarage(res: SaveGarageResponse): Buffer {
return buf;
}
export function saveGarage2(res: SaveGarageResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x0086, 0x0000);
buf.writeUInt16LE(res.status, 0x0004);
return buf;
}
+10 -1
View File
@@ -1,6 +1,6 @@
import { SaveNewCarResponse } from "../response/saveNewCar";
export function saveNewCar(res: SaveNewCarResponse): Buffer {
export function saveNewCar1(res: SaveNewCarResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x007a, 0x0000);
@@ -8,3 +8,12 @@ export function saveNewCar(res: SaveNewCarResponse): Buffer {
return buf;
}
export function saveNewCar2(res: SaveNewCarResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x0076, 0x0000);
buf.writeUInt8(res.status, 0x0004);
return buf;
}
+9 -1
View File
@@ -1,9 +1,17 @@
import { SaveTimeAttackResponse } from "../response/saveTimeAttack";
export function saveTimeAttack(res: SaveTimeAttackResponse): Buffer {
export function saveTimeAttack1(res: SaveTimeAttackResponse): Buffer {
const buf = Buffer.alloc(0x00b0);
buf.writeUInt16LE(0x00ce, 0x0000);
return buf;
}
export function saveTimeAttack2(res: SaveTimeAttackResponse): Buffer {
const buf = Buffer.alloc(0x00f0);
buf.writeUInt16LE(0x00cd, 0x0000);
return buf;
}
+9 -1
View File
@@ -1,9 +1,17 @@
import { SaveTopicResponse } from "../response/saveTopic";
export function saveTopic(res: SaveTopicResponse) {
export function saveTopic1(res: SaveTopicResponse) {
const buf = Buffer.alloc(0x05d0);
buf.writeInt16LE(0x009b, 0x0000);
return buf;
}
export function saveTopic2(res: SaveTopicResponse) {
const buf = Buffer.alloc(0x05d0);
buf.writeInt16LE(0x0092, 0x0000);
return buf;
}
+10 -1
View File
@@ -1,6 +1,6 @@
import { UnlockProfileResponse } from "../response/unlockProfile";
export function unlockProfile(res: UnlockProfileResponse) {
export function unlockProfile1(res: UnlockProfileResponse) {
const buf = Buffer.alloc(0x0010);
buf.writeInt16LE(0x0070, 0x0000);
@@ -8,3 +8,12 @@ export function unlockProfile(res: UnlockProfileResponse) {
return buf;
}
export function unlockProfile2(res: UnlockProfileResponse) {
const buf = Buffer.alloc(0x0010);
buf.writeInt16LE(0x006c, 0x0000);
buf.writeInt8(res.status, 0x0004);
return buf;
}
@@ -0,0 +1,18 @@
import { UpdateExpeditionResponse } from "../response/updateExpedition";
export function updateExpedition(res: UpdateExpeditionResponse) {
let buf: Buffer;
if (res.expeditionType == 0) {
buf = Buffer.alloc(0x0040);
buf.writeInt16LE(1, 0x0000);
} else {
buf = Buffer.alloc(0x19f0);
buf.writeInt16LE(0x0140, 0x0000);
// Experiments used to live here, removed because they're a not-really
// working mess. Hopefully, one day there will be beautiful code
// here, that works and makes people happy. One day.
}
return buf;
}
@@ -1,14 +1,35 @@
import iconv from "iconv-lite";
import { UpdateProvisionalStoreRankResponse } from "../response/updateProvisionalStoreRank";
import { writeSjisStr } from "../../util/bin";
export function updateProvisionalStoreRank(
export function updateProvisionalStoreRank1(
res: UpdateProvisionalStoreRankResponse
) {
const buf = Buffer.alloc(0x02b0);
buf.writeInt16LE(0x0083, 0x0000);
for (let i = 0; i < 10; i++) {
const offset = 0x0008 + i * 0x44;
const row = res.rows[i];
if (row !== undefined) {
buf.writeInt16LE(row.field_0000, offset + 0x0000);
buf.writeInt32LE(row.field_0004, offset + 0x0004);
writeSjisStr(buf, offset + 0x0010, offset + 0x003b, row.field_0010);
writeSjisStr(buf, offset + 0x003b, offset + 0x0044, row.field_003B);
}
}
return buf;
}
export function updateProvisionalStoreRank2(
res: UpdateProvisionalStoreRankResponse
) {
const buf = Buffer.alloc(0x02b0);
buf.writeInt16LE(0x007d, 0x0000);
for (let i = 0; i < 10; i++) {
const offset = 0x44 + i;
const row = res.rows[i];
@@ -16,8 +37,8 @@ export function updateProvisionalStoreRank(
if (row !== undefined) {
buf.writeInt16LE(row.field_0000, offset + 0x0000);
buf.writeInt32LE(row.field_0004, offset + 0x0004);
iconv.encode(row.field_0010, "shift_jis").copy(buf, offset + 0x0010);
iconv.encode(row.field_003B, "shift_jis").copy(buf, offset + 0x003b);
writeSjisStr(buf, offset + 0x0010, offset + 0x003b, row.field_0010);
writeSjisStr(buf, offset + 0x003b, offset + 0x0044, row.field_003B);
}
}
+17 -22
View File
@@ -1,25 +1,8 @@
import {
UpdateStoryClearNumResponse,
UpdateStoryClearNumResponse1,
UpdateStoryClearNumResponse2,
} from "../response/updateStoryClearNum";
import { UpdateStoryClearNumResponse } from "../response/updateStoryClearNum";
export function updateStoryClearNum(res: UpdateStoryClearNumResponse): Buffer {
switch (res.format) {
case 1:
return updateStoryClearNum1(res);
case 2:
return updateStoryClearNum2(res);
default:
const exhaust: never = res;
throw new Error(`Unsupported data format ${res["format"]}`);
}
}
function updateStoryClearNum1(res: UpdateStoryClearNumResponse1): Buffer {
export function updateStoryClearNum1(
res: UpdateStoryClearNumResponse
): Buffer {
const buf = Buffer.alloc(0x0220);
buf.writeInt16LE(0x0080, 0x0000);
@@ -27,10 +10,22 @@ function updateStoryClearNum1(res: UpdateStoryClearNumResponse1): Buffer {
return buf;
}
function updateStoryClearNum2(res: UpdateStoryClearNumResponse2): Buffer {
export function updateStoryClearNum2(
res: UpdateStoryClearNumResponse
): Buffer {
const buf = Buffer.alloc(0x04f0);
buf.writeInt16LE(0x013e, 0x0000);
return buf;
}
export function updateStoryClearNum3(
res: UpdateStoryClearNumResponse
): Buffer {
const buf = Buffer.alloc(0x0510);
buf.writeInt16LE(0x013e, 0x0000);
return buf;
}
+10 -1
View File
@@ -1,6 +1,6 @@
import { UpdateTeamLeaderResponse } from "../response/updateTeamLeader";
export function updateTeamLeader(res: UpdateTeamLeaderResponse): Buffer {
export function updateTeamLeader1(res: UpdateTeamLeaderResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x008b, 0x0000);
@@ -8,3 +8,12 @@ export function updateTeamLeader(res: UpdateTeamLeaderResponse): Buffer {
return buf;
}
export function updateTeamLeader2(res: UpdateTeamLeaderResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x0084, 0x0000);
buf.writeUInt32LE(res.status, 0x0004);
return buf;
}
+10 -1
View File
@@ -1,6 +1,6 @@
import { UpdateTeamMemberResponse } from "../response/updateTeamMember";
export function updateTeamMember(res: UpdateTeamMemberResponse): Buffer {
export function updateTeamMember1(res: UpdateTeamMemberResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x0074, 0x0000);
@@ -8,3 +8,12 @@ export function updateTeamMember(res: UpdateTeamMemberResponse): Buffer {
return buf;
}
export function updateTeamMember2(res: UpdateTeamMemberResponse): Buffer {
const buf = Buffer.alloc(0x0010);
buf.writeUInt16LE(0x0070, 0x0000);
buf.writeUInt32LE(res.status, 0x0004);
return buf;
}
+3 -1
View File
@@ -26,7 +26,9 @@ export async function _fixupPrevTeam(
// (need to look up new leader's db id from aime id. ick)
const newLeader = remaining[remaining.length - 1];
const newLeaderId = await w.profile().find(newLeader.profile.aimeId);
const newLeaderId = await w
.profile()
.find(newLeader.profile.aimeId, newLeader.profile.version);
await w.teamMembers().makeLeader(prevTeamId, newLeaderId);
}
+3 -2
View File
@@ -44,9 +44,9 @@ export async function createAutoTeam(
req: CreateAutoTeamRequest
): Promise<CreateAutoTeamResponse> {
const now = new Date();
const { aimeId } = req;
const { aimeId, version } = req;
const peek = await w.teamAuto().peek();
const peek = await w.teamAuto().peek(version);
let nextAuto: TeamAuto;
//
@@ -100,6 +100,7 @@ export async function createAutoTeam(
// Register the new team, make the requestor its leader
const spec = {
version,
name,
nameBg: autoTeams[nameIdx].nameBg,
nameFx: 0,
+20 -8
View File
@@ -1,21 +1,22 @@
import { MissionState } from "../model/mission";
import { Profile } from "../model/profile";
import { Settings } from "../model/settings";
import { Story } from "../model/story";
import { Story, StoryRow } from "../model/story";
import { Unlocks } from "../model/unlocks";
import { CreateProfileRequest } from "../request/createProfile";
import { GenericResponse } from "../response/generic";
import { CreateProfileResponse } from "../response/createProfile";
import { Repositories } from "../repo";
export async function createProfile(
w: Repositories,
req: CreateProfileRequest
): Promise<GenericResponse> {
const { aimeId, name } = req;
): Promise<CreateProfileResponse> {
const { aimeId, version, name } = req;
const now = new Date();
const profile: Profile = {
aimeId,
version,
name,
lv: 1,
exp: 0,
@@ -27,8 +28,19 @@ export async function createProfile(
};
const missions: MissionState = { team: [], solo: [] };
const settings: Settings = { music: 0, pack: 13640, aura: 0, paperCup: 0, gauges: 5 };
const story: Story = { x: 0, y: 0, rows: [] };
const settings: Settings = {
music: 0,
pack: 13640,
aura: 0,
paperCup: 0,
gauges: 5,
drivingStyle: 0, // Not supported until idz2
};
const story: Story = {
x: 0,
y: 0,
rows: new Map<number, StoryRow>(),
};
const unlocks: Unlocks = {
auras: 1,
cup: 0,
@@ -51,7 +63,7 @@ export async function createProfile(
await w.teamReservations().commitHack(aimeId);
return {
type: "generic_res",
status: aimeId, // "Generic response" my fucking *ass*
type: "create_profile_res",
aimeId: aimeId,
};
}
+2 -1
View File
@@ -7,7 +7,7 @@ export async function createTeam(
w: Repositories,
req: CreateTeamRequest
): Promise<CreateTeamResponse> {
const profileId = await w.profile().find(req.aimeId);
const profileId = await w.profile().find(req.aimeId, req.version);
const prevTeamId = await w.teamMembers().findTeam(profileId);
const now = new Date();
@@ -15,6 +15,7 @@ export async function createTeam(
const teamSpec = {
name: req.teamName,
version: req.version,
nameBg: req.nameBg,
nameFx: 0,
registerTime: now,
+1 -1
View File
@@ -6,7 +6,7 @@ export async function discoverProfile(
w: Repositories,
req: DiscoverProfileRequest
): Promise<DiscoverProfileResponse> {
const profileId = await w.profile().peek(req.aimeId);
const profileId = await w.profile().peek(req.aimeId, req.version);
return {
type: "discover_profile_res",

Some files were not shown because too many files have changed in this diff Show More