mirror of
https://github.com/djhackersdev/minime.git
synced 2026-03-22 02:04:19 -05:00
Compare commits
No commits in common. "master" and "v012" have entirely different histories.
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
|
@ -1,8 +1,5 @@
|
|||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.rulers": [79],
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
|
|
|
|||
73
CHANGELOG.md
73
CHANGELOG.md
|
|
@ -1,70 +1,53 @@
|
|||
# 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)
|
||||
- Chunithm: Add stubs to support Amazon Plus (Felix)
|
||||
|
||||
## v012
|
||||
|
||||
- Chunithm: Fix large data loads (NeumPhis)
|
||||
- Chunithm: Add some support for older versions (NeumPhis)
|
||||
- Chunithm: Fix large data loads (Felix)
|
||||
- Chunithm: Enable all events (Felix)
|
||||
- Chunithm: Remove server maintenance period (Felix)
|
||||
- Chunithm: Fix data loss (esterTion)
|
||||
- Chunithm: Add some support for older versions (esterTion)
|
||||
- IDZ: Team-related fixes (Emi Midnight)
|
||||
- Add support for newer AIME card services (cxm)
|
||||
- Chunithm: Enable ticket system (Rob)
|
||||
- Chunithm: Implement Recent Rating feature (seika1)
|
||||
- Chunithm: Fix User Activity feature (seika1)
|
||||
* Chunithm: Fix large data loads (NeumPhis)
|
||||
* Chunithm: Add some support for older versions (NeumPhis)
|
||||
* Chunithm: Fix large data loads (Matt Bilker)
|
||||
* Chunithm: Enable all events (Matt Bilker)
|
||||
* Chunithm: Remove server maintenance period (Matt Bilker)
|
||||
* Chunithm: Fix data loss (esterTion)
|
||||
* Chunithm: Add some support for older versions (esterTion)
|
||||
* IDZ: Team-related fixes (Emi Midnight)
|
||||
* Add support for newer AIME card services (cxm)
|
||||
* Chunithm: Enable ticket system (Rob)
|
||||
* Chunithm: Implement Recent Rating feature (seika1)
|
||||
* Chunithm: Fix User Activity feature (seika1)
|
||||
|
||||
## v011
|
||||
|
||||
- IDZ: Fix loading of >10 cars in garage (Tau)
|
||||
* IDZ: Fix loading of >10 cars in garage (Tau)
|
||||
|
||||
## v010
|
||||
|
||||
- Chunithm: Bug fix: Fix new database creation and repair affected databases. Again. (Tau)
|
||||
Switch to a different SQLite native code extension. C compiler is no longer required. (Tau)
|
||||
* Chunithm: Bug fix: Fix new database creation and repair affected databases. Again. (Tau)
|
||||
Switch to a different SQLite native code extension. C compiler is no longer required. (Tau)
|
||||
|
||||
## v009
|
||||
|
||||
- IDZ: Display team banners in attract loop rankings (Tau)
|
||||
- Chunithm: Bug fix: Fix new database creation and repair affected databases (Tau)
|
||||
* IDZ: Display team banners in attract loop rankings (Tau)
|
||||
* Chunithm: Bug fix: Fix new database creation and repair affected databases (Tau)
|
||||
|
||||
## v008
|
||||
|
||||
- Implement auras in IDZ (BemaniWitch)
|
||||
- Fix user ratings (NeumPhis)
|
||||
- Load and save course results (NeumPhis)
|
||||
- Load and save user duel list (Felix)
|
||||
* Implement auras in IDZ (BemaniWitch)
|
||||
* Fix user ratings (NeumPhis)
|
||||
* Load and save course results (NeumPhis)
|
||||
* Load and save user duel list (Felix)
|
||||
|
||||
## v007
|
||||
|
||||
- Fix IDZ team creation (Tau)
|
||||
- Fix startup script working directory (Tau)
|
||||
* Fix IDZ team creation (Tau)
|
||||
* Fix startup script working directory (Tau)
|
||||
|
||||
## v006
|
||||
|
||||
- Fix Chunithm HTTP POST size limit (Tau)
|
||||
* Fix Chunithm HTTP POST size limit (Tau)
|
||||
|
||||
## v005
|
||||
|
||||
- Initial public release (Tau)
|
||||
- Chunithm v1.30.00 Amazon
|
||||
- Initial D Zero v1.21.00
|
||||
- Initial D Zero v1.31.00
|
||||
* Initial public release (Tau)
|
||||
* Chunithm v1.30.00 Amazon
|
||||
* Initial D Zero v1.21.00
|
||||
* Initial D Zero v1.31.00
|
||||
|
|
|
|||
5831
package-lock.json
generated
5831
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -27,12 +27,12 @@
|
|||
"@types/jest": "^24.0.11",
|
||||
"@types/multiparty": "^0.0.32",
|
||||
"@types/node": "^12.11.7",
|
||||
"jest": "^26.4.2",
|
||||
"jest-haste-map": "^26.3.0",
|
||||
"jest": "^24.5.0",
|
||||
"jest-haste-map": "^24.5.0",
|
||||
"jest-resolve": "^24.5.0",
|
||||
"prettier": "^1.16.4",
|
||||
"ts-jest": "^26.3.0",
|
||||
"typescript": "^3.8.3",
|
||||
"ts-jest": "^24.0.0",
|
||||
"typescript": "^3.6.4",
|
||||
"utility-types": "^3.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ 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,
|
||||
|
|
@ -20,7 +16,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", "version")
|
||||
constraint "idz_profile_player_uq" unique ("player_id")
|
||||
);
|
||||
|
||||
create table "idz_chara" (
|
||||
|
|
@ -104,27 +100,7 @@ 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,
|
||||
"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")
|
||||
"gauges" integer not null
|
||||
);
|
||||
|
||||
create table "idz_story_state" (
|
||||
|
|
@ -144,7 +120,6 @@ 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",
|
||||
|
|
@ -227,13 +202,12 @@ 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 ("version", "ext_id")
|
||||
constraint "idz_team_uq" unique ("ext_id")
|
||||
);
|
||||
|
||||
create table "idz_team_auto" (
|
||||
|
|
@ -241,7 +215,8 @@ create table "idz_team_auto" (
|
|||
references "idz_team"("id")
|
||||
on delete cascade,
|
||||
"serial_no" integer not null,
|
||||
"name_idx" integer not null
|
||||
"name_idx" integer not null,
|
||||
constraint "idz_team_auto_uq" unique ("serial_no", "name_idx")
|
||||
);
|
||||
|
||||
create table "idz_team_member" (
|
||||
|
|
@ -265,25 +240,3 @@ 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")
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
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";
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
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";
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
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";
|
||||
|
|
@ -104,32 +104,30 @@ async function migratedb(
|
|||
}
|
||||
|
||||
export default async function checkdb(db: DataSource): Promise<void> {
|
||||
let schemaver: number | undefined;
|
||||
const stmt = sql.select("schemaver").from("meta");
|
||||
let maybe: number | undefined;
|
||||
|
||||
await db.maintenance(async txn => {
|
||||
const stmt = sql.select("schemaver").from("meta");
|
||||
try {
|
||||
const row = await db.transaction(txn => txn.fetchRow(stmt));
|
||||
|
||||
try {
|
||||
const row = await txn.fetchRow(stmt);
|
||||
|
||||
if (row !== undefined) {
|
||||
schemaver = parseInt(row.schemaver!);
|
||||
}
|
||||
} catch (e) {
|
||||
return await initdb(txn);
|
||||
if (row !== undefined) {
|
||||
maybe = parseInt(row.schemaver!);
|
||||
}
|
||||
} catch (e) {
|
||||
return db.transaction(initdb);
|
||||
}
|
||||
|
||||
if (schemaver === undefined) {
|
||||
throw new Error(
|
||||
"Database corrupted: `meta` table singleton row is missing"
|
||||
);
|
||||
}
|
||||
if (maybe === undefined) {
|
||||
throw new Error(
|
||||
"Database corrupted: `meta` table singleton row is missing"
|
||||
);
|
||||
}
|
||||
|
||||
schemaver = await migratedb(txn, schemaver);
|
||||
});
|
||||
const schemaver = maybe;
|
||||
const newver = await db.transaction(txn => migratedb(txn, schemaver));
|
||||
|
||||
if (schemaver !== undefined) {
|
||||
debug("Upgraded database to version %s", schemaver);
|
||||
if (newver !== undefined) {
|
||||
debug("Upgraded database to version %s", newver);
|
||||
|
||||
await db.vacuum();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
import { Repositories } from "../repo";
|
||||
import { GetUserFavoriteMusicRequest } from "../request/getUserFavoriteMusic";
|
||||
import { GetUserFavoriteMusicResponse } from "../response/getUserFavoriteMusic";
|
||||
import { readAimeId } from "../proto/base";
|
||||
|
||||
export default async function getUserFavoriteMusic(
|
||||
rep: Repositories,
|
||||
req: GetUserFavoriteMusicRequest
|
||||
): Promise<GetUserFavoriteMusicResponse> {
|
||||
const aimeId = readAimeId(req.userId);
|
||||
|
||||
const profileId = await rep.userData().lookup(aimeId);
|
||||
|
||||
/*
|
||||
* `Chunithm Amazon Plus` does not appear to save a favorites list and there
|
||||
* is no user-accessible favorites function from what I can tell.
|
||||
*/
|
||||
return {
|
||||
userId: req.userId,
|
||||
length: "0",
|
||||
userFavoriteMusicList: [],
|
||||
};
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ import getUserCourse from "./getUserCourse";
|
|||
import getUserData from "./getUserData";
|
||||
import getUserDataEx from "./getUserDataEx";
|
||||
import getUserDuel from "./getUserDuel";
|
||||
import getUserFavoriteMusic from "./getUserFavoriteMusic";
|
||||
import getUserItem from "./getUserItem";
|
||||
import getUserMap from "./getUserMap";
|
||||
import getUserMusic from "./getUserMusic";
|
||||
|
|
@ -100,7 +99,6 @@ export default function chunithm(db: DataSource) {
|
|||
wrapper.rpc("/GetUserDataApi", getUserData);
|
||||
wrapper.rpc("/GetUserDataExApi", getUserDataEx);
|
||||
wrapper.rpc("/GetUserDuelApi", getUserDuel);
|
||||
wrapper.rpc("/GetUserFavoriteMusicApi", getUserFavoriteMusic);
|
||||
wrapper.rpc("/GetUserItemApi", getUserItem);
|
||||
wrapper.rpc("/GetUserMapApi", getUserMap);
|
||||
wrapper.rpc("/GetUserMusicApi", getUserMusic);
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
export interface GetUserFavoriteMusicRequest {
|
||||
/** Integer, AiMe ID */
|
||||
userId: string;
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
export interface GetUserFavoriteMusicResponse {
|
||||
/** Integer, AiMe ID */
|
||||
userId: string;
|
||||
|
||||
/** Integer, number of results returned */
|
||||
length: string;
|
||||
|
||||
/** TBD */
|
||||
userFavoriteMusicList: [];
|
||||
}
|
||||
|
|
@ -29,8 +29,7 @@ export class SqlUserActivityRepository implements UserActivityRepository {
|
|||
.from("cm_user_activity")
|
||||
.where("profile_id", profileId)
|
||||
.where("kind", kind)
|
||||
.orderBy("sort_number DESC")
|
||||
.limit(100);
|
||||
.orderBy("sort_number DESC");
|
||||
|
||||
const rows = await this._txn.fetchRows(stmt);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,4 @@
|
|||
export function readBigInt(buf: Buffer) {
|
||||
let result = 0n;
|
||||
|
||||
for (let i = 0; i < buf.length; i++) {
|
||||
const shift = 8n * BigInt(i);
|
||||
const byte = buf.readUInt8(i);
|
||||
|
||||
result |= BigInt(byte) << shift;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function writeBigInt(n: bigint, length: number) {
|
||||
export function byteString(n: bigint, length: number) {
|
||||
const result = Buffer.alloc(length);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { Decipher, Cipher, createCipheriv, createDecipheriv } from "crypto";
|
||||
|
||||
import { ByteStream } from "../../util/stream";
|
||||
|
||||
export const BLOCK_SIZE = 16;
|
||||
|
||||
export default class AesEcbStream implements ByteStream {
|
||||
private readonly _dec: Decipher;
|
||||
private readonly _enc: Cipher;
|
||||
|
||||
constructor(private _stm: ByteStream, keybuf: Buffer) {
|
||||
this._dec = createDecipheriv("aes-128-ecb", keybuf, null).setAutoPadding(
|
||||
false
|
||||
);
|
||||
this._enc = createCipheriv("aes-128-ecb", keybuf, null).setAutoPadding(
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
close() {
|
||||
this._stm.close();
|
||||
}
|
||||
|
||||
async read(nbytes: number): Promise<Buffer> {
|
||||
if (nbytes % BLOCK_SIZE !== 0) {
|
||||
throw new Error("Attempted to read partial cipher block");
|
||||
}
|
||||
|
||||
const ciphertext = await this._stm.read(nbytes);
|
||||
|
||||
return this._dec.update(ciphertext);
|
||||
}
|
||||
|
||||
async write(buf: Buffer): Promise<void> {
|
||||
if (buf.length % BLOCK_SIZE !== 0) {
|
||||
throw new Error("Attempted to write partial cipher block");
|
||||
}
|
||||
|
||||
const ciphertext = this._enc.update(buf);
|
||||
|
||||
await this._stm.write(ciphertext);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import setup from "./setup";
|
||||
|
||||
export { BLOCK_SIZE } from "./aes";
|
||||
export { ClientHello } from "./setup";
|
||||
export default setup;
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
import { Socket } from "net";
|
||||
|
||||
import AesEcbStream from "./aes";
|
||||
import { readBigInt, modPow, writeBigInt } from "./bigint";
|
||||
import IoByteStream, { ByteStream } from "../../util/stream";
|
||||
|
||||
interface RsaKey {
|
||||
N: bigint;
|
||||
d: bigint;
|
||||
e: bigint;
|
||||
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,
|
||||
d: 1163847742215766215216916151663017691387519688859977157498780867776436010396072628219119707788340687440419444081289736279466637153082223960965411473296473n,
|
||||
e: 3961365081960959178294197133768419551060435043430437330799371731939550352626564261219865471710058480523874787120718634318364066605378505537556570049131337n,
|
||||
hashN: 2662304617,
|
||||
};
|
||||
|
||||
// Proof-of-concept, so we only use one fixed session key
|
||||
const aesKey = Buffer.from("ffddeeccbbaa99887766554433221100", "hex");
|
||||
|
||||
function writeServerHello(aesKey: Buffer, rsaKey: RsaKey): Buffer {
|
||||
const M = readBigInt(aesKey);
|
||||
const keyEnc = modPow(M, rsaKey.e, rsaKey.N);
|
||||
const result = Buffer.alloc(0x48);
|
||||
|
||||
result.set(writeBigInt(keyEnc, 0x40), 0x00);
|
||||
result.writeUInt32LE(0x01020304, 0x40); // Meaning is unknown
|
||||
result.writeUInt32LE(rsaKey.hashN, 0x44);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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."
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
pcbId: buf.slice(0x04, 0x0f).toString("ascii"),
|
||||
protocol: buf.slice(0x10, 0x13).toString("ascii"),
|
||||
model: buf.slice(0x18, 0x0c).toString("ascii"),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function setup(socket: Socket): Promise<IdzConnection> {
|
||||
const tcpStream = new IoByteStream(socket);
|
||||
const serverHello = writeServerHello(aesKey, rsaKey);
|
||||
|
||||
await tcpStream.write(serverHello);
|
||||
|
||||
const aesStream = new AesEcbStream(tcpStream, aesKey);
|
||||
|
||||
const response = await aesStream.read(0x30);
|
||||
|
||||
if (response.length !== 0x30) {
|
||||
throw new Error("Truncated client hello");
|
||||
}
|
||||
|
||||
const clientHello = readClientHello(response);
|
||||
|
||||
return { aesStream, clientHello };
|
||||
}
|
||||
8
src/idz/decoder/checkTeamName.ts
Normal file
8
src/idz/decoder/checkTeamName.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { CheckTeamNameRequest } from "../request/checkTeamName";
|
||||
|
||||
checkTeamName.msgCode = 0x00a2;
|
||||
checkTeamName.msgLen = 0x0040;
|
||||
|
||||
export function checkTeamName(buf: Buffer): CheckTeamNameRequest {
|
||||
return { type: "check_team_name_req" };
|
||||
}
|
||||
14
src/idz/decoder/createAutoTeam.ts
Normal file
14
src/idz/decoder/createAutoTeam.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { CreateAutoTeamRequest } from "../request/createAutoTeam";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
createAutoTeam.msgCode = 0x007b;
|
||||
createAutoTeam.msgLen = 0x0010;
|
||||
|
||||
export function createAutoTeam(buf: Buffer): CreateAutoTeamRequest {
|
||||
return {
|
||||
type: "create_auto_team_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
field_0008: buf.readUInt32LE(0x0008),
|
||||
field_000C: buf.readUInt8(0x000c),
|
||||
};
|
||||
}
|
||||
24
src/idz/decoder/createProfile.ts
Normal file
24
src/idz/decoder/createProfile.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import iconv from "iconv-lite";
|
||||
|
||||
import { car } from "./_car";
|
||||
import { chara } from "./_chara";
|
||||
import { CreateProfileRequest } from "../request/createProfile";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
createProfile.msgCode = 0x0066;
|
||||
createProfile.msgLen = 0x00c0;
|
||||
|
||||
export function createProfile(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"
|
||||
),
|
||||
field_0034: buf.readUInt32LE(0x0034),
|
||||
car: car(buf.slice(0x0040, 0x00a0)),
|
||||
chara: chara(buf.slice(0x00a0, 0x00b4)),
|
||||
};
|
||||
}
|
||||
24
src/idz/decoder/createTeam.ts
Normal file
24
src/idz/decoder/createTeam.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import iconv from "iconv-lite";
|
||||
|
||||
import { CreateTeamRequest } from "../request/createTeam";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
createTeam.msgCode = 0x0071;
|
||||
createTeam.msgLen = 0x0050;
|
||||
|
||||
export function createTeam(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"
|
||||
),
|
||||
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"),
|
||||
};
|
||||
}
|
||||
12
src/idz/decoder/discoverProfile.ts
Normal file
12
src/idz/decoder/discoverProfile.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { DiscoverProfileRequest } from "../request/discoverProfile";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
discoverProfile.msgCode = 0x006b;
|
||||
discoverProfile.msgLen = 0x0010;
|
||||
|
||||
export function discoverProfile(buf: Buffer): DiscoverProfileRequest {
|
||||
return {
|
||||
type: "discover_profile_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
206
src/idz/decoder/index.ts
Normal file
206
src/idz/decoder/index.ts
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
import logger from "debug";
|
||||
import { Transform } from "stream";
|
||||
|
||||
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 { loadServerList } from "./loadServerList";
|
||||
import { loadStocker } from "./loadStocker";
|
||||
import { loadTeam } from "./loadTeam";
|
||||
import { loadTeamRanking, loadTeamRanking2 } from "./loadTeamRanking";
|
||||
import { loadTopTen1 } from "./loadTopTen1";
|
||||
import { loadTopTen2 } from "./loadTopTen2";
|
||||
import { lockGarage } from "./lockGarage";
|
||||
import { lockProfile } from "./lockProfile";
|
||||
import { msg00AD } from "./msg00AD";
|
||||
import { saveExpedition1, saveExpedition2 } from "./saveExpedition";
|
||||
import { saveGarage } from "./saveGarage";
|
||||
import { saveNewCar } 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 {
|
||||
updateStoryClearNum1,
|
||||
updateStoryClearNum2,
|
||||
} 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";
|
||||
|
||||
const debug = logger("app:idz:decoder");
|
||||
|
||||
export type ReaderFn = ((buf: Buffer) => Request) & {
|
||||
msgCode: number;
|
||||
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,
|
||||
];
|
||||
|
||||
const readerFns = new Map<number, ReaderFn>();
|
||||
const msgLengths = new Map<number, number>();
|
||||
|
||||
for (const fn of funcList) {
|
||||
readerFns.set(fn.msgCode, fn);
|
||||
msgLengths.set(fn.msgCode, fn.msgLen);
|
||||
}
|
||||
|
||||
function readHeader(buf: Buffer) {
|
||||
return {
|
||||
blah: "blah",
|
||||
};
|
||||
}
|
||||
|
||||
export class Decoder extends Transform {
|
||||
state: Buffer;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
readableObjectMode: true,
|
||||
writableObjectMode: true,
|
||||
});
|
||||
|
||||
this.state = Buffer.alloc(0);
|
||||
}
|
||||
|
||||
_transform(chunk: Buffer, encoding, callback) {
|
||||
this.state = Buffer.concat([this.state, chunk]);
|
||||
|
||||
// Read header
|
||||
|
||||
if (this.state.length < 0x04) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
const magic = this.state.readUInt32LE(0);
|
||||
|
||||
if (magic !== 0x01020304) {
|
||||
return callback(
|
||||
new Error(
|
||||
"Invalid magic number, cryptographic processing probably incorrect."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.length < 0x30) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
const header = readHeader(this.state);
|
||||
|
||||
if (this.state.length < 0x32) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
const msgCode = this.state.readUInt16LE(0x30);
|
||||
const msgLen = msgLengths.get(msgCode);
|
||||
|
||||
if (msgLen === undefined) {
|
||||
return callback(
|
||||
new Error(
|
||||
`Unknown command code ${msgCode.toString(16)}, cannot continue`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.length < 0x30 + msgLen) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
const reqBuf = this.state.slice(0, 0x30 + msgLen);
|
||||
const payloadBuf = reqBuf.slice(0x30);
|
||||
|
||||
if (debug.enabled) {
|
||||
debug("Raw: %s", reqBuf.toString("hex"));
|
||||
debug("Header: %j", header);
|
||||
}
|
||||
|
||||
const reader = readerFns.get(msgCode);
|
||||
|
||||
if (reader === undefined) {
|
||||
return callback(
|
||||
new Error(`No reader for command code ${msgCode.toString(16)}`)
|
||||
);
|
||||
}
|
||||
|
||||
const payload = reader(payloadBuf);
|
||||
|
||||
debug("Payload: %j", payload);
|
||||
|
||||
return callback(null, payload);
|
||||
}
|
||||
}
|
||||
30
src/idz/decoder/load2on2.ts
Normal file
30
src/idz/decoder/load2on2.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
import { Load2on2Request1, Load2on2Request2 } from "../request/load2on2";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
load2on2_v1.msgCode = 0x00b0;
|
||||
load2on2_v1.msgLen = 0x0010;
|
||||
|
||||
export function load2on2_v1(buf: Buffer): Load2on2Request1 {
|
||||
return {
|
||||
type: "load_2on2_req",
|
||||
format: 1,
|
||||
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;
|
||||
|
||||
export function load2on2_v2(buf: Buffer): Load2on2Request2 {
|
||||
return {
|
||||
type: "load_2on2_req",
|
||||
format: 2,
|
||||
field_0002: buf.readUInt16LE(0x0002),
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
teamId: buf.readUInt32LE(0x0008) as ExtId<Team>,
|
||||
};
|
||||
}
|
||||
8
src/idz/decoder/loadConfig.ts
Normal file
8
src/idz/decoder/loadConfig.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { LoadConfigRequest } from "../request/loadConfig";
|
||||
|
||||
loadConfig.msgCode = 0x0004;
|
||||
loadConfig.msgLen = 0x0050;
|
||||
|
||||
export function loadConfig(): LoadConfigRequest {
|
||||
return { type: "load_config_req" };
|
||||
}
|
||||
8
src/idz/decoder/loadConfig2.ts
Normal file
8
src/idz/decoder/loadConfig2.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { LoadConfigRequest2 } from "../request/loadConfig2";
|
||||
|
||||
loadConfig2.msgCode = 0x00ab;
|
||||
loadConfig2.msgLen = 0x0010;
|
||||
|
||||
export function loadConfig2(): LoadConfigRequest2 {
|
||||
return { type: "load_config_v2_req" };
|
||||
}
|
||||
12
src/idz/decoder/loadEventInfo.ts
Normal file
12
src/idz/decoder/loadEventInfo.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { LoadEventInfoRequest } from "../request/loadEventInfo";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadEventInfo.msgCode = 0x00be;
|
||||
loadEventInfo.msgLen = 0x0010;
|
||||
|
||||
export function loadEventInfo(buf: Buffer): LoadEventInfoRequest {
|
||||
return {
|
||||
type: "load_event_info_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
12
src/idz/decoder/loadGacha.ts
Normal file
12
src/idz/decoder/loadGacha.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { LoadGachaRequest } from "../request/loadGacha";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadGacha.msgCode = 0x00c1;
|
||||
loadGacha.msgLen = 0x0010;
|
||||
|
||||
export function loadGacha(buf: Buffer): LoadGachaRequest {
|
||||
return {
|
||||
type: "load_gacha_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
14
src/idz/decoder/loadGarage.ts
Normal file
14
src/idz/decoder/loadGarage.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { LoadGarageRequest } from "../request/loadGarage";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadGarage.msgCode = 0x0090;
|
||||
loadGarage.msgLen = 0x0010;
|
||||
|
||||
export function loadGarage(buf: Buffer): LoadGarageRequest {
|
||||
return {
|
||||
type: "load_garage_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
fetchOffset: buf.readUInt8(0x0008),
|
||||
field_000A: buf.readUInt16LE(0x000a),
|
||||
};
|
||||
}
|
||||
|
|
@ -1,32 +1,27 @@
|
|||
import { LoadGeneralRewardRequest } from "../request/loadGeneralReward";
|
||||
import { AimeId } from "../../../model";
|
||||
import {
|
||||
LoadGeneralRewardRequest1,
|
||||
LoadGeneralRewardRequest2,
|
||||
} from "../request/loadGeneralReward";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadGeneralReward1.msgCode = 0x009c;
|
||||
loadGeneralReward1.msgLen = 0x0010;
|
||||
|
||||
export function loadGeneralReward1(buf: Buffer): LoadGeneralRewardRequest {
|
||||
export function loadGeneralReward1(buf: Buffer): LoadGeneralRewardRequest1 {
|
||||
return {
|
||||
type: "load_general_reward_req",
|
||||
format: 1,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
|
||||
loadGeneralReward2.msgCode = 0x093b;
|
||||
loadGeneralReward2.msgCode = 0x013b;
|
||||
loadGeneralReward2.msgLen = 0x0010;
|
||||
|
||||
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 {
|
||||
export function loadGeneralReward2(buf: Buffer): LoadGeneralRewardRequest2 {
|
||||
return {
|
||||
type: "load_general_reward_req",
|
||||
format: 2,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
15
src/idz/decoder/loadGhost.ts
Normal file
15
src/idz/decoder/loadGhost.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { LoadGhostRequest } from "../request/loadGhost";
|
||||
|
||||
loadGhost.msgCode = 0x00a0;
|
||||
loadGhost.msgLen = 0x0010;
|
||||
|
||||
export function loadGhost(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),
|
||||
};
|
||||
}
|
||||
29
src/idz/decoder/loadProfile.ts
Normal file
29
src/idz/decoder/loadProfile.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import {
|
||||
LoadProfileRequest2,
|
||||
LoadProfileRequest3,
|
||||
} from "../request/loadProfile";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadProfile2.msgCode = 0x0067;
|
||||
loadProfile2.msgLen = 0x0020;
|
||||
|
||||
export function loadProfile2(buf: Buffer): LoadProfileRequest2 {
|
||||
return {
|
||||
type: "load_profile_req",
|
||||
format: 2,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
luid: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
||||
};
|
||||
}
|
||||
|
||||
loadProfile3.msgCode = 0x0012f;
|
||||
loadProfile3.msgLen = 0x0020;
|
||||
|
||||
export function loadProfile3(buf: Buffer): LoadProfileRequest3 {
|
||||
return {
|
||||
type: "load_profile_req",
|
||||
format: 3,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
luid: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
||||
};
|
||||
}
|
||||
10
src/idz/decoder/loadRewardTable.ts
Normal file
10
src/idz/decoder/loadRewardTable.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { LoadRewardTableRequest } from "../request/loadRewardTable";
|
||||
|
||||
loadRewardTable.msgCode = 0x0086;
|
||||
loadRewardTable.msgLen = 0x0010;
|
||||
|
||||
export function loadRewardTable(): LoadRewardTableRequest {
|
||||
return {
|
||||
type: "load_reward_table_req",
|
||||
};
|
||||
}
|
||||
12
src/idz/decoder/loadStocker.ts
Normal file
12
src/idz/decoder/loadStocker.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { LoadStockerRequest } from "../request/loadStocker";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadStocker.msgCode = 0x00a7;
|
||||
loadStocker.msgLen = 0x0010;
|
||||
|
||||
export function loadStocker(buf: Buffer): LoadStockerRequest {
|
||||
return {
|
||||
type: "load_stocker_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
16
src/idz/decoder/loadTeam.ts
Normal file
16
src/idz/decoder/loadTeam.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { LoadTeamRequest } from "../request/loadTeam";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
|
||||
loadTeam.msgCode = 0x0077;
|
||||
loadTeam.msgLen = 0x0010;
|
||||
|
||||
export function loadTeam(buf: Buffer): LoadTeamRequest {
|
||||
const extId = buf.readUInt32LE(0x0008);
|
||||
|
||||
return {
|
||||
type: "load_team_req",
|
||||
aimeId: buf.readUInt32LE(0x0004),
|
||||
teamExtId: extId !== 0xffffffff ? (extId as ExtId<Team>) : undefined,
|
||||
};
|
||||
}
|
||||
21
src/idz/decoder/loadTeamRanking.ts
Normal file
21
src/idz/decoder/loadTeamRanking.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { LoadTeamRankingRequest } from "../request/loadTeamRanking";
|
||||
|
||||
loadTeamRanking.msgCode = 0x00b9;
|
||||
loadTeamRanking.msgLen = 0x0010;
|
||||
|
||||
export function loadTeamRanking(buf: Buffer): LoadTeamRankingRequest {
|
||||
return {
|
||||
type: "load_team_ranking_req",
|
||||
};
|
||||
}
|
||||
|
||||
// not sure what the difference is...
|
||||
|
||||
loadTeamRanking2.msgCode = 0x00bb;
|
||||
loadTeamRanking2.msgLen = 0x0010;
|
||||
|
||||
export function loadTeamRanking2(buf: Buffer): LoadTeamRankingRequest {
|
||||
return {
|
||||
type: "load_team_ranking_req",
|
||||
};
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import {
|
|||
LoadTopTenRequest,
|
||||
LoadTopTenRequestSelector,
|
||||
} from "../request/loadTopTen";
|
||||
import { AimeId } from "../../../model";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadTopTen1.msgCode = 0x00b5;
|
||||
loadTopTen1.msgLen = 0x00e0;
|
||||
|
|
@ -24,7 +24,6 @@ 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
|
||||
|
|
@ -4,7 +4,7 @@ import {
|
|||
LoadTopTenRequest,
|
||||
LoadTopTenRequestSelector,
|
||||
} from "../request/loadTopTen";
|
||||
import { AimeId } from "../../../model";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadTopTen2.msgCode = 0x012c;
|
||||
loadTopTen2.msgLen = 0x0110;
|
||||
|
|
@ -24,7 +24,6 @@ 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
|
||||
|
|
@ -34,34 +33,3 @@ 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,
|
||||
};
|
||||
}
|
||||
11
src/idz/decoder/lockGarage.ts
Normal file
11
src/idz/decoder/lockGarage.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { LockGarageRequest } from "../request/lockGarage";
|
||||
|
||||
lockGarage.msgCode = 0x00a9;
|
||||
lockGarage.msgLen = 0x0010;
|
||||
|
||||
export function lockGarage(buf: Buffer): LockGarageRequest {
|
||||
return {
|
||||
type: "lock_garage_request",
|
||||
field_0004: buf.readUInt32LE(0x0004),
|
||||
};
|
||||
}
|
||||
13
src/idz/decoder/lockProfile.ts
Normal file
13
src/idz/decoder/lockProfile.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { LockProfileRequest } from "../request/lockProfile";
|
||||
|
||||
lockProfile.msgCode = 0x0069;
|
||||
lockProfile.msgLen = 0x0020;
|
||||
|
||||
export function lockProfile(buf: Buffer): LockProfileRequest {
|
||||
return {
|
||||
type: "lock_profile_req",
|
||||
aimeId: buf.readUInt32LE(0x0004),
|
||||
pcbId: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
||||
field_0018: buf.readUInt16LE(0x0018),
|
||||
};
|
||||
}
|
||||
13
src/idz/decoder/lockProfileExtend.ts
Normal file
13
src/idz/decoder/lockProfileExtend.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { LockProfileExtendRequest } from "../request/lockProfileExtend";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
lockProfileExtend.msgCode = 0x006d;
|
||||
lockProfileExtend.msgLen = 0x0020;
|
||||
|
||||
export function lockProfileExtend(buf: Buffer): LockProfileExtendRequest {
|
||||
return {
|
||||
type: "lock_profile_extend_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
luid: buf.slice(0x0008, buf.indexOf("\0")).toString("ascii"),
|
||||
};
|
||||
}
|
||||
8
src/idz/decoder/msg00AD.ts
Normal file
8
src/idz/decoder/msg00AD.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { Message00AD } from "../request/msg00AD";
|
||||
|
||||
msg00AD.msgCode = 0x00ad;
|
||||
msg00AD.msgLen = 0x061c;
|
||||
|
||||
export function msg00AD(buf: Buffer): Message00AD {
|
||||
return { type: "msg_00AD_req" };
|
||||
}
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
import { SaveExpeditionRequest } from "../request/saveExpedition";
|
||||
import {
|
||||
SaveExpeditionRequest1,
|
||||
SaveExpeditionRequest2,
|
||||
} from "../request/saveExpedition";
|
||||
|
||||
saveExpedition1.msgCode = 0x008c;
|
||||
saveExpedition1.msgLen = 0x0010;
|
||||
|
||||
export function saveExpedition1(buf: Buffer): SaveExpeditionRequest {
|
||||
export function saveExpedition1(buf: Buffer): SaveExpeditionRequest1 {
|
||||
return {
|
||||
type: "save_expedition_req",
|
||||
format: 1,
|
||||
field_0004: buf.readUInt32LE(0x0004),
|
||||
};
|
||||
}
|
||||
|
|
@ -13,9 +17,10 @@ export function saveExpedition1(buf: Buffer): SaveExpeditionRequest {
|
|||
saveExpedition2.msgCode = 0x013f;
|
||||
saveExpedition2.msgLen = 0x0010;
|
||||
|
||||
export function saveExpedition2(buf: Buffer): SaveExpeditionRequest {
|
||||
export function saveExpedition2(buf: Buffer): SaveExpeditionRequest2 {
|
||||
return {
|
||||
type: "save_expedition_req",
|
||||
format: 2,
|
||||
field_0004: buf.readUInt32LE(0x0004),
|
||||
};
|
||||
}
|
||||
22
src/idz/decoder/saveGarage.ts
Normal file
22
src/idz/decoder/saveGarage.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { car } from "./_car";
|
||||
import { SaveGarageRequest } from "../request/saveGarage";
|
||||
|
||||
saveGarage.msgCode = 0x008e;
|
||||
saveGarage.msgLen = 0x0090;
|
||||
|
||||
export function saveGarage(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",
|
||||
aimeId: buf.readUInt32LE(0x0004),
|
||||
payload: car(buf.slice(0x0008, 0x0068)),
|
||||
field_0068,
|
||||
field_0080: buf.readUInt8(0x0080),
|
||||
field_0081: buf.readUInt8(0x0081) !== 0,
|
||||
};
|
||||
}
|
||||
16
src/idz/decoder/saveNewCar.ts
Normal file
16
src/idz/decoder/saveNewCar.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { car } from "./_car";
|
||||
import { SaveNewCarRequest } from "../request/saveNewCar";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
saveNewCar.msgCode = 0x0079;
|
||||
saveNewCar.msgLen = 0x0090;
|
||||
|
||||
export function saveNewCar(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"),
|
||||
car: car(buf.slice(0x0020, 0x0080)),
|
||||
field_0080: buf.readUInt32LE(0x0080),
|
||||
};
|
||||
}
|
||||
|
|
@ -1,33 +1,31 @@
|
|||
import { car } from "./_car";
|
||||
import { mission } from "./_mission";
|
||||
import { BackgroundCode, CourseNo, TitleCode } from "../model/base";
|
||||
import { StoryCell, StoryRow } from "../model/story";
|
||||
import { SaveProfileRequest } from "../request/saveProfile";
|
||||
import { SaveProfileRequest2 } from "../request/saveProfile";
|
||||
import { bitmap } from "./_bitmap";
|
||||
import { AimeId } from "../../../model";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
saveProfile2.msgCode = 0x0068;
|
||||
saveProfile2.msgLen = 0x0940;
|
||||
|
||||
export function saveProfile2(buf: Buffer): SaveProfileRequest {
|
||||
const storyRows = new Map<number, StoryRow>();
|
||||
export function saveProfile2(buf: Buffer): SaveProfileRequest2 {
|
||||
const storyRows = new Array();
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const cells = new Map<number, StoryCell>();
|
||||
const cells = new Array();
|
||||
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 c = 0; // Added in idz2
|
||||
const cell = { a, b, c };
|
||||
const cell = { a, b };
|
||||
|
||||
cells.set(j, cell);
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
const row = { cells };
|
||||
|
||||
storyRows.set(i, row);
|
||||
storyRows.push(row);
|
||||
}
|
||||
|
||||
const coursePlays = new Map<CourseNo, number>();
|
||||
|
|
@ -47,8 +45,8 @@ export function saveProfile2(buf: Buffer): SaveProfileRequest {
|
|||
|
||||
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),
|
||||
|
|
@ -96,7 +94,6 @@ export function saveProfile2(buf: Buffer): SaveProfileRequest {
|
|||
aura: buf.readUInt8(0x002c),
|
||||
paperCup: buf.readUInt8(0x00f6),
|
||||
gauges: buf.readUInt8(0x00f7),
|
||||
drivingStyle: 0, // Not supported until idz2
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,35 +1,33 @@
|
|||
import { car } from "./_car";
|
||||
import { mission } from "./_mission";
|
||||
import { BackgroundCode, CourseNo, TitleCode } from "../model/base";
|
||||
import { StoryCell, StoryRow } from "../model/story";
|
||||
import { SaveProfileRequest } from "../request/saveProfile";
|
||||
import { SaveProfileRequest2 } from "../request/saveProfile";
|
||||
import { bitmap } from "./_bitmap";
|
||||
import { AimeId } from "../../../model";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
saveProfile3.msgCode = 0x0138;
|
||||
saveProfile3.msgLen = 0x0a70;
|
||||
|
||||
export function saveProfile3(buf: Buffer): SaveProfileRequest {
|
||||
const storyRows = new Map<number, StoryRow>();
|
||||
export function saveProfile3(buf: Buffer): SaveProfileRequest2 {
|
||||
const storyRows = new Array();
|
||||
|
||||
// Story layout has changed somewhat...
|
||||
|
||||
for (let i = 0; i < 27; i++) {
|
||||
const cells = new Map<number, StoryCell>();
|
||||
const cells = new Array();
|
||||
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 c = 0; // Added in idz2
|
||||
const cell = { a, b, c };
|
||||
const cell = { a, b };
|
||||
|
||||
cells.set(j, cell);
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
const row = { cells };
|
||||
|
||||
storyRows.set(i, row);
|
||||
storyRows.push(row);
|
||||
}
|
||||
|
||||
const coursePlays = new Map<CourseNo, number>();
|
||||
|
|
@ -49,8 +47,8 @@ export function saveProfile3(buf: Buffer): SaveProfileRequest {
|
|||
|
||||
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),
|
||||
|
|
@ -98,7 +96,6 @@ export function saveProfile3(buf: Buffer): SaveProfileRequest {
|
|||
aura: buf.readUInt8(0x002c),
|
||||
paperCup: buf.readUInt8(0x00f6),
|
||||
gauges: buf.readUInt8(0x00f7),
|
||||
drivingStyle: 0, // Not supported until idz2
|
||||
},
|
||||
};
|
||||
}
|
||||
23
src/idz/decoder/saveSettings.ts
Normal file
23
src/idz/decoder/saveSettings.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
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);
|
||||
|
||||
return {
|
||||
type: "save_settings_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
dpoint: buf.readUInt32LE(0x0008),
|
||||
settings: {
|
||||
music: buf.readUInt16LE(0x0002),
|
||||
pack: buf.readUInt32LE(0x000c),
|
||||
paperCup: buf.readUInt8(0x0011),
|
||||
gauges: buf.readUInt8(0x0012),
|
||||
aura: buf.readUInt8(0x0013),
|
||||
},
|
||||
field_0010: buf.readUInt8(0x0010),
|
||||
};
|
||||
}
|
||||
19
src/idz/decoder/saveStocker.ts
Normal file
19
src/idz/decoder/saveStocker.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { bitmap } from "./_bitmap";
|
||||
import { chara } from "./_chara";
|
||||
import { CarSelector } from "../model/car";
|
||||
import { SaveStockerRequest } from "../request/saveStocker";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
saveStocker.msgCode = 0x00a6;
|
||||
saveStocker.msgLen = 0x00c0;
|
||||
|
||||
export function saveStocker(buf: Buffer): SaveStockerRequest {
|
||||
return {
|
||||
type: "save_stocker_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
|
||||
backgrounds: bitmap(buf.slice(0x0008, 0x002c)),
|
||||
selectedCar: buf.readUInt16LE(0x009c) as CarSelector,
|
||||
chara: chara(buf.slice(0x009e, 0x00b2)),
|
||||
};
|
||||
}
|
||||
15
src/idz/decoder/saveTeamBanner.ts
Normal file
15
src/idz/decoder/saveTeamBanner.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { SaveTeamBannerRequest } from "../request/saveTeamBanner";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
|
||||
saveTeamBanner.msgCode = 0x0089;
|
||||
saveTeamBanner.msgLen = 0x0010;
|
||||
|
||||
export function saveTeamBanner(buf: Buffer): SaveTeamBannerRequest {
|
||||
return {
|
||||
type: "save_team_banner_req",
|
||||
teamExtId: buf.readUInt32LE(0x0004) as ExtId<Team>,
|
||||
nameBg: buf.readUInt32LE(0x0008),
|
||||
nameFx: buf.readUInt32LE(0x000c),
|
||||
};
|
||||
}
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
import { RouteNo } from "../model/base";
|
||||
import { CarSelector } from "../model/car";
|
||||
import { SaveTimeAttackRequest } from "../request/saveTimeAttack";
|
||||
import { AimeId } from "../../../model";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
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,
|
||||
|
|
@ -49,35 +48,3 @@ 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),
|
||||
};
|
||||
}
|
||||
13
src/idz/decoder/saveTopic.ts
Normal file
13
src/idz/decoder/saveTopic.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { SaveTopicRequest } from "../request/saveTopic";
|
||||
|
||||
saveTopic.msgCode = 0x009a;
|
||||
saveTopic.msgLen = 0x0010;
|
||||
|
||||
export function saveTopic(buf: Buffer): SaveTopicRequest {
|
||||
const aimeId = buf.readUInt32LE(0x0004);
|
||||
|
||||
return {
|
||||
type: "save_topic_req",
|
||||
aimeId: aimeId !== 0xffffffff ? aimeId : undefined,
|
||||
};
|
||||
}
|
||||
12
src/idz/decoder/unlockProfile.ts
Normal file
12
src/idz/decoder/unlockProfile.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { UnlockProfileRequest } from "../request/unlockProfile";
|
||||
|
||||
unlockProfile.msgCode = 0x006f;
|
||||
unlockProfile.msgLen = 0x0020;
|
||||
|
||||
export function unlockProfile(buf: Buffer): UnlockProfileRequest {
|
||||
return {
|
||||
type: "unlock_profile_req",
|
||||
aimeId: buf.readUInt32LE(0x0004),
|
||||
pcbId: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
||||
};
|
||||
}
|
||||
13
src/idz/decoder/updateProvisionalStoreRank.ts
Normal file
13
src/idz/decoder/updateProvisionalStoreRank.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { UpdateProvisionalStoreRankRequest } from "../request/updateProvisionalStoreRank";
|
||||
|
||||
updateProvisionalStoreRank.msgCode = 0x0082;
|
||||
updateProvisionalStoreRank.msgLen = 0x0010;
|
||||
|
||||
export function updateProvisionalStoreRank(
|
||||
buf: Buffer
|
||||
): UpdateProvisionalStoreRankRequest {
|
||||
return {
|
||||
type: "update_provisional_store_rank_req",
|
||||
aimeId: buf.readUInt32LE(0x0004),
|
||||
};
|
||||
}
|
||||
28
src/idz/decoder/updateStoryClearNum.ts
Normal file
28
src/idz/decoder/updateStoryClearNum.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import {
|
||||
UpdateStoryClearNumRequest1,
|
||||
UpdateStoryClearNumRequest2,
|
||||
} from "../request/updateStoryClearNum";
|
||||
|
||||
updateStoryClearNum1.msgCode = 0x007f;
|
||||
updateStoryClearNum1.msgLen = 0x0010;
|
||||
|
||||
export function updateStoryClearNum1(
|
||||
buf: Buffer
|
||||
): UpdateStoryClearNumRequest1 {
|
||||
return {
|
||||
type: "update_story_clear_num_req",
|
||||
format: 1,
|
||||
};
|
||||
}
|
||||
|
||||
updateStoryClearNum2.msgCode = 0x013d;
|
||||
updateStoryClearNum2.msgLen = 0x0010;
|
||||
|
||||
export function updateStoryClearNum2(
|
||||
buf: Buffer
|
||||
): UpdateStoryClearNumRequest2 {
|
||||
return {
|
||||
type: "update_story_clear_num_req",
|
||||
format: 2,
|
||||
};
|
||||
}
|
||||
16
src/idz/decoder/updateTeamLeader.ts
Normal file
16
src/idz/decoder/updateTeamLeader.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
import { UpdateTeamLeaderRequest } from "../request/updateTeamLeader";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
updateTeamLeader.msgCode = 0x008a;
|
||||
updateTeamLeader.msgLen = 0x0020;
|
||||
|
||||
export function updateTeamLeader(buf: Buffer): UpdateTeamLeaderRequest {
|
||||
return {
|
||||
type: "update_team_leader_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
teamExtId: buf.readUInt32LE(0x0008) as ExtId<Team>,
|
||||
field_000C: buf.slice(0x000c, buf.indexOf("\0", 0x000c)).toString("ascii"),
|
||||
};
|
||||
}
|
||||
16
src/idz/decoder/updateTeamMember.ts
Normal file
16
src/idz/decoder/updateTeamMember.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
import { UpdateTeamMemberRequest } from "../request/updateTeamMember";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
updateTeamMember.msgCode = 0x0073;
|
||||
updateTeamMember.msgLen = 0x0010;
|
||||
|
||||
export function updateTeamMember(buf: Buffer): UpdateTeamMemberRequest {
|
||||
return {
|
||||
type: "update_team_member_req",
|
||||
action: buf.readUInt8(0x0004) === 0 ? "add" : "remove",
|
||||
aimeId: buf.readUInt32LE(0x0008) as AimeId,
|
||||
teamExtId: buf.readUInt32LE(0x000c) as ExtId<Team>,
|
||||
};
|
||||
}
|
||||
12
src/idz/decoder/updateTeamPoints.ts
Normal file
12
src/idz/decoder/updateTeamPoints.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { UpdateTeamPointsRequest } from "../request/updateTeamPoints";
|
||||
|
||||
updateTeamPoints.msgCode = 0x0081;
|
||||
updateTeamPoints.msgLen = 0x0010;
|
||||
|
||||
export function updateTeamPoints(buf: Buffer): UpdateTeamPointsRequest {
|
||||
return {
|
||||
type: "update_team_points_req",
|
||||
field_0004: buf.readUInt32LE(0x0004),
|
||||
field_0008: buf.readUInt32LE(0x0008),
|
||||
};
|
||||
}
|
||||
12
src/idz/decoder/updateUiReport.ts
Normal file
12
src/idz/decoder/updateUiReport.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { UpdateUiReportRequest } from "../request/updateUiReport";
|
||||
|
||||
updateUiReport.msgCode = 0x0084;
|
||||
updateUiReport.msgLen = 0x0410;
|
||||
|
||||
export function updateUiReport(buf: Buffer): UpdateUiReportRequest {
|
||||
return {
|
||||
type: "update_ui_report_req",
|
||||
field_02: buf.readUInt16LE(0x0002),
|
||||
field_04: buf.readUInt16LE(0x0004),
|
||||
};
|
||||
}
|
||||
8
src/idz/decoder/updateUserLog.ts
Normal file
8
src/idz/decoder/updateUserLog.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { UpdateUserLogRequest } from "../request/updateUserLog";
|
||||
|
||||
updateUserLog.msgCode = 0x00bd;
|
||||
updateUserLog.msgLen = 0x0050;
|
||||
|
||||
export function updateUserLog(buf: Buffer): UpdateUserLogRequest {
|
||||
return { type: "update_user_log_req" };
|
||||
}
|
||||
18
src/idz/encoder/_chara.ts
Normal file
18
src/idz/encoder/_chara.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { Chara } from "../model/chara";
|
||||
|
||||
export function encodeChara(chara: Chara): Buffer {
|
||||
const buf = Buffer.alloc(0x0014);
|
||||
|
||||
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); // Swapped on save
|
||||
buf.writeUInt16LE(chara.title, 0x12); // Swapped on save
|
||||
|
||||
return buf;
|
||||
}
|
||||
59
src/idz/encoder/_team.ts
Normal file
59
src/idz/encoder/_team.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import iconv from "iconv-lite";
|
||||
|
||||
import { CreateAutoTeamResponse } from "../response/createAutoTeam";
|
||||
import { LoadTeamResponse } from "../response/loadTeam";
|
||||
import { encodeChara } from "./_chara";
|
||||
|
||||
export function _team(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.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);
|
||||
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 = 0x011c + i * 0x005c;
|
||||
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);
|
||||
iconv.encode(profile.name + "\0", "shift_jis").copy(buf, base + 0x0004);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
10
src/idz/encoder/checkTeamName.ts
Normal file
10
src/idz/encoder/checkTeamName.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { CheckTeamNameResponse } from "../response/checkTeamName";
|
||||
|
||||
export function checkTeamName(res: CheckTeamNameResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x0010);
|
||||
|
||||
buf.writeUInt16LE(0x00a3, 0x0000);
|
||||
buf.writeUInt32LE(res.status, 0x0004);
|
||||
|
||||
return buf;
|
||||
}
|
||||
11
src/idz/encoder/createTeam.ts
Normal file
11
src/idz/encoder/createTeam.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { CreateTeamResponse } from "../response/createTeam";
|
||||
|
||||
export function createTeam(res: CreateTeamResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x0010);
|
||||
|
||||
buf.writeUInt16LE(0x0072, 0x0000);
|
||||
buf.writeUInt32LE(res.status, 0x0004);
|
||||
buf.writeUInt32LE(res.teamExtId, 0x0008);
|
||||
|
||||
return buf;
|
||||
}
|
||||
10
src/idz/encoder/discoverProfile.ts
Normal file
10
src/idz/encoder/discoverProfile.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { DiscoverProfileResponse } from "../response/discoverProfile";
|
||||
|
||||
export function discoverProfile(res: DiscoverProfileResponse) {
|
||||
const buf = Buffer.alloc(0x0010);
|
||||
|
||||
buf.writeInt16LE(0x006c, 0x0000);
|
||||
buf.writeInt8(res.exists ? 1 : 0, 0x0004);
|
||||
|
||||
return buf;
|
||||
}
|
||||
10
src/idz/encoder/generic.ts
Normal file
10
src/idz/encoder/generic.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { GenericResponse } from "../response/generic";
|
||||
|
||||
export function generic(res: GenericResponse) {
|
||||
const buf = Buffer.alloc(0x0020);
|
||||
|
||||
buf.writeInt16LE(0x0001, 0x0000);
|
||||
buf.writeInt32LE(res.status || 0, 0x0004);
|
||||
|
||||
return buf;
|
||||
}
|
||||
167
src/idz/encoder/index.ts
Normal file
167
src/idz/encoder/index.ts
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
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 { 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 { Response } from "../response";
|
||||
|
||||
const debug = logger("app:idz:encoder");
|
||||
|
||||
function encode(res: Response): Buffer {
|
||||
switch (res.type) {
|
||||
case "check_team_name_res":
|
||||
return checkTeamName(res);
|
||||
|
||||
case "create_auto_team_res":
|
||||
return _team(res);
|
||||
|
||||
case "create_team_res":
|
||||
return createTeam(res);
|
||||
|
||||
case "discover_profile_res":
|
||||
return discoverProfile(res);
|
||||
|
||||
case "generic_res":
|
||||
return generic(res);
|
||||
|
||||
case "load_2on2_res":
|
||||
return load2on2(res);
|
||||
|
||||
case "load_config_res":
|
||||
return loadConfig(res);
|
||||
|
||||
case "load_config_v2_res":
|
||||
return loadConfig2(res);
|
||||
|
||||
case "load_event_info_res":
|
||||
return loadEventInfo(res);
|
||||
|
||||
case "load_gacha_res":
|
||||
return loadGacha(res);
|
||||
|
||||
case "load_garage_res":
|
||||
return loadGarage(res);
|
||||
|
||||
case "load_general_reward_res":
|
||||
return loadGeneralReward(res);
|
||||
|
||||
case "load_ghost_res":
|
||||
return loadGhost(res);
|
||||
|
||||
case "load_profile_res":
|
||||
return loadProfile(res);
|
||||
|
||||
case "load_reward_table_res":
|
||||
return loadRewardTable(res);
|
||||
|
||||
case "load_server_list_res":
|
||||
return loadServerList(res);
|
||||
|
||||
case "load_stocker_res":
|
||||
return loadStocker(res);
|
||||
|
||||
case "load_team_res":
|
||||
return _team(res);
|
||||
|
||||
case "load_team_ranking_res":
|
||||
return loadTeamRanking(res);
|
||||
|
||||
case "load_top_ten_res":
|
||||
return loadTopTen(res);
|
||||
|
||||
case "lock_profile_extend_res":
|
||||
return lockProfileExtend(res);
|
||||
|
||||
case "lock_profile_res":
|
||||
return lockProfile(res);
|
||||
|
||||
case "save_expedition_res":
|
||||
return saveExpedition(res);
|
||||
|
||||
case "save_garage_res":
|
||||
return saveGarage(res);
|
||||
|
||||
case "save_new_car_res":
|
||||
return saveNewCar(res);
|
||||
|
||||
case "save_time_attack_res":
|
||||
return saveTimeAttack(res);
|
||||
|
||||
case "unlock_profile_res":
|
||||
return unlockProfile(res);
|
||||
|
||||
case "update_provisional_store_rank_res":
|
||||
return updateProvisionalStoreRank(res);
|
||||
|
||||
case "update_story_clear_num_res":
|
||||
return updateStoryClearNum(res);
|
||||
|
||||
case "update_team_leader_res":
|
||||
return updateTeamLeader(res);
|
||||
|
||||
case "update_team_member_res":
|
||||
return updateTeamMember(res);
|
||||
|
||||
case "save_topic_res":
|
||||
return saveTopic(res);
|
||||
|
||||
default:
|
||||
const exhaustCheck: never = res;
|
||||
|
||||
throw new Error(`No writer fn for ${res["type"]}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class Encoder extends Transform {
|
||||
constructor() {
|
||||
super({
|
||||
readableObjectMode: true,
|
||||
writableObjectMode: true,
|
||||
});
|
||||
}
|
||||
|
||||
_transform(res: Response, encoding, callback) {
|
||||
debug("Object: %j", res);
|
||||
|
||||
const buf = encode(res);
|
||||
|
||||
if (debug.enabled) {
|
||||
debug("Encoded: %s", buf.toString("hex"));
|
||||
}
|
||||
|
||||
if (buf.readInt16LE(0) === 0) {
|
||||
throw new Error("Missing message type code");
|
||||
}
|
||||
|
||||
return callback(null, buf);
|
||||
}
|
||||
}
|
||||
37
src/idz/encoder/load2on2.ts
Normal file
37
src/idz/encoder/load2on2.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import {
|
||||
Load2on2Response,
|
||||
Load2on2Response1,
|
||||
Load2on2Response2,
|
||||
} 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 {
|
||||
const buf = Buffer.alloc(0x04c0);
|
||||
|
||||
buf.writeInt16LE(0x00b1, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Same size but presumably incompatible somehow
|
||||
function load2on2_v2(res: Load2on2Response2): Buffer {
|
||||
const buf = Buffer.alloc(0x04c0);
|
||||
|
||||
buf.writeInt16LE(0x0133, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
11
src/idz/encoder/loadConfig.ts
Normal file
11
src/idz/encoder/loadConfig.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { LoadConfigResponse } from "../response/loadConfig";
|
||||
|
||||
export function loadConfig(res: LoadConfigResponse) {
|
||||
const buf = Buffer.alloc(0x01a0);
|
||||
|
||||
buf.writeInt16LE(0x0005, 0x0000);
|
||||
buf.writeInt8(res.status, 0x0002);
|
||||
buf.writeUInt16LE(res.serverVersion, 0x0016);
|
||||
|
||||
return buf;
|
||||
}
|
||||
10
src/idz/encoder/loadConfig2.ts
Normal file
10
src/idz/encoder/loadConfig2.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
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
src/idz/encoder/loadEventInfo.ts
Normal file
9
src/idz/encoder/loadEventInfo.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { LoadEventInfoResponse } from "../response/loadEventInfo";
|
||||
|
||||
export function loadEventInfo(res: LoadEventInfoResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x01b0);
|
||||
|
||||
buf.writeUInt16LE(0x00bf, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
10
src/idz/encoder/loadGacha.ts
Normal file
10
src/idz/encoder/loadGacha.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { LoadGachaResponse } from "../response/loadGacha";
|
||||
|
||||
export function loadGacha(res: LoadGachaResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x0090);
|
||||
|
||||
buf.writeUInt16LE(0x00c2, 0x0000);
|
||||
buf.writeUInt8(res.awardedToday ? 0x01 : 0x00, 0x0002);
|
||||
|
||||
return buf;
|
||||
}
|
||||
15
src/idz/encoder/loadGarage.ts
Normal file
15
src/idz/encoder/loadGarage.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { encodeCar } from "./_car";
|
||||
import { LoadGarageResponse } from "../response/loadGarage";
|
||||
|
||||
export function loadGarage(res: LoadGarageResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x03d0);
|
||||
|
||||
buf.writeUInt16LE(0x0091, 0x0000);
|
||||
buf.writeUInt16LE(res.cars.length, 0x0002);
|
||||
|
||||
for (let i = 0; i < res.cars.length; i++) {
|
||||
encodeCar(res.cars[i]).copy(buf, 0x0004 + 0x0060 * i);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
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);
|
||||
|
|
@ -14,7 +15,7 @@ export function loadGeneralReward(res: LoadGeneralRewardResponse) {
|
|||
const item = res.items[i];
|
||||
const base = 0x04 + 0x50 * i;
|
||||
|
||||
writeSjisStr(buf, base + 0x04, base + 0x2c, item.field_04);
|
||||
iconv.encode(item.field_04, "shift_jis").copy(buf, base + 0x04);
|
||||
buf.writeUInt32LE(item.field_2C, base + 0x2c);
|
||||
buf.writeUInt8(item.field_38, base + 0x38);
|
||||
buf.writeUInt8(item.field_39, base + 0x39);
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { LoadGhostResponse } from "../response/loadGhost";
|
||||
|
||||
function _loadGhost(msgCode: number, res: LoadGhostResponse): Buffer {
|
||||
export function loadGhost(res: LoadGhostResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x0070);
|
||||
|
||||
// No idea what any of this even does.
|
||||
|
||||
buf.writeUInt16LE(msgCode, 0x0000);
|
||||
buf.writeUInt16LE(0x00a1, 0x0000);
|
||||
buf.writeUInt16LE(0x0005, 0x0002); // Chunk presence flags: 4 | 1
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
|
|
@ -29,11 +29,3 @@ function _loadGhost(msgCode: number, 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
src/idz/encoder/loadProfile.ts
Normal file
22
src/idz/encoder/loadProfile.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
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"]}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { LoadProfileResponse } from "../response/loadProfile";
|
||||
import { LoadProfileResponse1 } from "../response/loadProfile";
|
||||
|
||||
// Sending this causes an error in v1.21, so it is currently unmapped and
|
||||
// unimplemented.
|
||||
|
||||
export function loadProfile1(res: LoadProfileResponse) {
|
||||
export function loadProfile1(res: LoadProfileResponse1) {
|
||||
const buf = Buffer.alloc(0x0c60);
|
||||
|
||||
buf.writeInt16LE(0x0064, 0x0000);
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import iconv from "iconv-lite";
|
||||
|
||||
import { encodeBitmap } from "./_bitmap";
|
||||
import { encodeCar } from "./_car";
|
||||
import { encodeChara1 } from "./_chara";
|
||||
import { encodeChara } from "./_chara";
|
||||
import { encodeMission } from "./_mission";
|
||||
import { LoadProfileResponse } from "../response/loadProfile";
|
||||
import { writeSjisStr } from "../../util/bin";
|
||||
import { LoadProfileResponse2 } from "../response/loadProfile";
|
||||
|
||||
export function loadProfile2(res: LoadProfileResponse) {
|
||||
export function loadProfile2(res: LoadProfileResponse2) {
|
||||
const buf = Buffer.alloc(0x0d30);
|
||||
|
||||
// FLAMETHROWER ANALYSIS (watch out for C strings)
|
||||
|
|
@ -37,22 +38,14 @@ export function loadProfile2(res: LoadProfileResponse) {
|
|||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const row = res.story.rows.get(i);
|
||||
for (let i = 0; i < 9 && i < res.story.rows.length; i++) {
|
||||
const row = res.story.rows[i];
|
||||
const rowOffset = 0x0228 + i * 0x26;
|
||||
|
||||
if (row === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let j = 0; j < 9; j++) {
|
||||
const cell = row.cells.get(j);
|
||||
for (let j = 0; j < 9 && j < row.cells.length; j++) {
|
||||
const cell = row.cells[j];
|
||||
const cellOffset = rowOffset + j * 4;
|
||||
|
||||
if (cell === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
buf.writeUInt16LE(cell.a, cellOffset + 0);
|
||||
buf.writeUInt16LE(cell.b, cellOffset + 2);
|
||||
}
|
||||
|
|
@ -94,11 +87,11 @@ export function loadProfile2(res: LoadProfileResponse) {
|
|||
buf.writeUInt32LE(res.settings.pack, 0x03d8);
|
||||
buf.writeUInt32LE(res.dpoint, 0x03e8);
|
||||
buf.writeUInt32LE(res.fame, 0x0404);
|
||||
writeSjisStr(buf, 0x03ee, 0x40e, res.name);
|
||||
iconv.encode(res.name + "\0", "shift_jis").copy(buf, 0x03ee);
|
||||
buf.writeUInt8(res.story.y, 0x0670);
|
||||
buf.writeUInt16LE(res.story.x, 0x06bc);
|
||||
encodeMission(res.missions.solo).copy(buf, 0x06e4);
|
||||
encodeChara1(res.chara).copy(buf, 0x070c);
|
||||
encodeChara(res.chara).copy(buf, 0x070c);
|
||||
encodeBitmap(res.titles, 0xb4).copy(buf, 0x720);
|
||||
buf.writeUInt8(res.settings.aura, 0x07d6);
|
||||
buf.writeUInt8(res.settings.paperCup, 0x07d9);
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import iconv from "iconv-lite";
|
||||
|
||||
import { encodeBitmap } from "./_bitmap";
|
||||
import { encodeCar } from "./_car";
|
||||
import { encodeChara1 } from "./_chara";
|
||||
import { encodeChara } from "./_chara";
|
||||
import { encodeMission } from "./_mission";
|
||||
import { LoadProfileResponse } from "../response/loadProfile";
|
||||
import { writeSjisStr } from "../../util/bin";
|
||||
import { LoadProfileResponse3 } from "../response/loadProfile";
|
||||
|
||||
export function loadProfile3(res: LoadProfileResponse) {
|
||||
export function loadProfile3(res: LoadProfileResponse3) {
|
||||
const buf = Buffer.alloc(0x0ea0);
|
||||
|
||||
// Initialize all TA grades to uhh... fuck knows
|
||||
|
|
@ -37,22 +38,14 @@ export function loadProfile3(res: LoadProfileResponse) {
|
|||
// 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++) {
|
||||
const row = res.story.rows.get(i);
|
||||
for (let i = 0; i < 27 && i < res.story.rows.length; i++) {
|
||||
const row = res.story.rows[i];
|
||||
const rowOffset = 0x0256 + i * 0x13;
|
||||
|
||||
if (row === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let j = 0; j < 9; j++) {
|
||||
const cell = row.cells.get(j);
|
||||
for (let j = 0; j < 9 && j < row.cells.length; j++) {
|
||||
const cell = row.cells[j];
|
||||
const cellOffset = rowOffset + j * 2;
|
||||
|
||||
if (cell === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
buf.writeUInt8(cell.a, cellOffset + 0);
|
||||
buf.writeUInt8(cell.b, cellOffset + 1);
|
||||
}
|
||||
|
|
@ -94,11 +87,11 @@ export function loadProfile3(res: LoadProfileResponse) {
|
|||
buf.writeUInt32LE(res.settings.pack, 0x04b4);
|
||||
buf.writeUInt32LE(res.dpoint, 0x04c4);
|
||||
buf.writeUInt32LE(res.fame, 0x04e0);
|
||||
writeSjisStr(buf, 0x04ca, 0x04ea, res.name);
|
||||
iconv.encode(res.name + "\0", "shift_jis").copy(buf, 0x04ca);
|
||||
buf.writeUInt8(res.story.y, 0x080c);
|
||||
buf.writeUInt16LE(res.story.x, 0x0828);
|
||||
encodeMission(res.missions.solo).copy(buf, 0x0858);
|
||||
encodeChara1(res.chara).copy(buf, 0x0880);
|
||||
encodeChara(res.chara).copy(buf, 0x0880);
|
||||
encodeBitmap(res.titles, 0xb4).copy(buf, 0x0894);
|
||||
buf.writeUInt8(res.settings.aura, 0x094a);
|
||||
buf.writeUInt8(res.settings.paperCup, 0x094d);
|
||||
9
src/idz/encoder/loadRewardTable.ts
Normal file
9
src/idz/encoder/loadRewardTable.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { LoadRewardTableResponse } from "../response/loadRewardTable";
|
||||
|
||||
export function loadRewardTable(res: LoadRewardTableResponse) {
|
||||
const buf = Buffer.alloc(0x01c0);
|
||||
|
||||
buf.writeInt16LE(0x0087, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
31
src/idz/encoder/loadServerList.ts
Normal file
31
src/idz/encoder/loadServerList.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { LoadServerListResponse } from "../response/loadServerList";
|
||||
|
||||
export function loadServerList(res: LoadServerListResponse) {
|
||||
const buf = Buffer.alloc(0x04b0);
|
||||
|
||||
buf.writeInt16LE(0x0007, 0x0000);
|
||||
buf.writeInt16LE(res.status, 0x0002);
|
||||
buf.write(res.userDb.addr, 0x0004);
|
||||
buf.writeInt16LE(res.userDb.tcp, 0x0084);
|
||||
buf.writeInt16LE(res.userDb.http, 0x0086);
|
||||
buf.write(res.matchAddr, 0x0088);
|
||||
buf.writeInt16LE(res.matchPort.tcp, 0x0108);
|
||||
buf.writeInt16LE(res.matchPort.udpSend, 0x010a);
|
||||
buf.writeInt16LE(res.matchPort.udpRecv, 0x010c);
|
||||
buf.writeInt16LE(res.tagMatchPort.tcp, 0x010e);
|
||||
buf.writeInt16LE(res.tagMatchPort.udpSend, 0x0110);
|
||||
buf.writeInt16LE(res.tagMatchPort.udpRecv, 0x0112);
|
||||
buf.write(res.event.addr, 0x0114);
|
||||
buf.writeInt16LE(res.event.tcp, 0x0194);
|
||||
buf.write(res.screenshot.addr, 0x0198);
|
||||
buf.writeInt16LE(res.screenshot.tcp, 0x0218);
|
||||
buf.write(res.pingReturn, 0x021c);
|
||||
buf.write(res.echo1.addr, 0x029c);
|
||||
buf.write(res.echo2.addr, 0x031c);
|
||||
buf.writeInt16LE(res.echo1.udp, 0x39c);
|
||||
buf.writeInt16LE(res.echo2.udp, 0x39e);
|
||||
buf.write(res.newsUrl, 0x03a0);
|
||||
buf.write(res.reportErrorUrl, 0x0424);
|
||||
|
||||
return buf;
|
||||
}
|
||||
12
src/idz/encoder/loadStocker.ts
Normal file
12
src/idz/encoder/loadStocker.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { encodeBitmap } from "./_bitmap";
|
||||
import { LoadStockerResponse } from "../response/loadStocker";
|
||||
|
||||
export function loadStocker(res: LoadStockerResponse) {
|
||||
const buf = Buffer.alloc(0x00a0);
|
||||
|
||||
buf.writeInt16LE(0x00a8, 0x0000);
|
||||
buf.writeUInt8(res.status, 0x0002);
|
||||
encodeBitmap(res.backgrounds, 0x24).copy(buf, 0x0003);
|
||||
|
||||
return buf;
|
||||
}
|
||||
10
src/idz/encoder/loadTeamRanking.ts
Normal file
10
src/idz/encoder/loadTeamRanking.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { LoadTeamRankingResponse } from "../response/loadTeamRanking";
|
||||
|
||||
export function loadTeamRanking(res: LoadTeamRankingResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x0ba0);
|
||||
|
||||
buf.writeUInt16LE(0x00ba, 0x0000);
|
||||
// Row stride is 0x94
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { writeSjisStr } from "../../util/bin";
|
||||
import iconv from "iconv-lite";
|
||||
|
||||
import { LoadTopTenResponse } from "../response/loadTopTen";
|
||||
|
||||
export function loadTopTen(res: LoadTopTenResponse): Buffer {
|
||||
|
|
@ -48,9 +49,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);
|
||||
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);
|
||||
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);
|
||||
buf.writeUInt32LE(row.team.nameBg, innerOff + 0x0074);
|
||||
buf.writeUInt16LE(row.team.nameFx, innerOff + 0x0078);
|
||||
buf.writeUInt8(row.field_7C, innerOff + 0x007c);
|
||||
|
|
@ -69,7 +70,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);
|
||||
writeSjisStr(buf, offset + 0x08, offset + 0x1c, trailer.name);
|
||||
iconv.encode(trailer.name, "shift_jis").copy(buf, offset + 0x08);
|
||||
} else {
|
||||
buf.writeUInt8(0xff, offset + 0x02);
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ const statusCodes = {
|
|||
obsolete: 0x0002,
|
||||
};
|
||||
|
||||
export function lockProfile1(res: LockProfileResponse) {
|
||||
export function lockProfile(res: LockProfileResponse) {
|
||||
const buf = Buffer.alloc(0x0020);
|
||||
|
||||
buf.writeInt16LE(0x006a, 0x0000);
|
||||
|
|
@ -16,14 +16,3 @@ export function lockProfile1(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
src/idz/encoder/lockProfileExtend.ts
Normal file
10
src/idz/encoder/lockProfileExtend.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { LockProfileExtendResponse } from "../response/lockProfileExtend";
|
||||
|
||||
export function lockProfileExtend(res: LockProfileExtendResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x0010);
|
||||
|
||||
buf.writeUInt16LE(0x006e, 0x0000);
|
||||
buf.writeUInt8(res.status, 0x0004);
|
||||
|
||||
return buf;
|
||||
}
|
||||
38
src/idz/encoder/saveExpedition.ts
Normal file
38
src/idz/encoder/saveExpedition.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import {
|
||||
SaveExpeditionResponse,
|
||||
SaveExpeditionResponse1,
|
||||
SaveExpeditionResponse2,
|
||||
} 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 {
|
||||
// in awe of the size of this lad
|
||||
const buf = Buffer.alloc(0x17c0);
|
||||
|
||||
buf.writeInt16LE(0x008d, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
function saveExpedition2(res: SaveExpeditionResponse2): Buffer {
|
||||
// absolute unit
|
||||
const buf = Buffer.alloc(0x18ac);
|
||||
|
||||
buf.writeUInt16LE(0x0140, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
10
src/idz/encoder/saveGarage.ts
Normal file
10
src/idz/encoder/saveGarage.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { SaveGarageResponse } from "../response/saveGarage";
|
||||
|
||||
export function saveGarage(res: SaveGarageResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x0010);
|
||||
|
||||
buf.writeUInt16LE(0x008f, 0x0000);
|
||||
buf.writeUInt16LE(res.status, 0x0002);
|
||||
|
||||
return buf;
|
||||
}
|
||||
10
src/idz/encoder/saveNewCar.ts
Normal file
10
src/idz/encoder/saveNewCar.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { SaveNewCarResponse } from "../response/saveNewCar";
|
||||
|
||||
export function saveNewCar(res: SaveNewCarResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x0010);
|
||||
|
||||
buf.writeUInt16LE(0x007a, 0x0000);
|
||||
buf.writeUInt8(res.status, 0x0002);
|
||||
|
||||
return buf;
|
||||
}
|
||||
9
src/idz/encoder/saveTimeAttack.ts
Normal file
9
src/idz/encoder/saveTimeAttack.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { SaveTimeAttackResponse } from "../response/saveTimeAttack";
|
||||
|
||||
export function saveTimeAttack(res: SaveTimeAttackResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x00b0);
|
||||
|
||||
buf.writeUInt16LE(0x00ce, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user