add idz repository layer
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
bin/
|
||||
node_modules/
|
||||
pki/
|
||||
state/
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export function bitmap(buf: Buffer): number[] {
|
||||
const result = new Array();
|
||||
|
||||
for (let byteNo = 0; byteNo < buf.length; byteNo++) {
|
||||
const byte = buf.readUInt8(byteNo);
|
||||
|
||||
for (let bitNo = 0; bitNo < 8; bitNo++) {
|
||||
if (byte & (1 << bitNo)) {
|
||||
result.push(8 * byteNo + bitNo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BackgroundCode, TitleCode } from "../model/base";
|
||||
import { Chara } from "../model/chara";
|
||||
|
||||
export function chara(buf: Buffer): Chara {
|
||||
@@ -10,5 +11,7 @@ export function chara(buf: Buffer): Chara {
|
||||
field_0A: buf.readUInt16LE(0x0a),
|
||||
field_0C: buf.readUInt16LE(0x0c),
|
||||
field_0E: buf.readUInt16LE(0x0e),
|
||||
title: buf.readUInt16LE(0x10) as TitleCode, // Swapped on load
|
||||
background: buf.readUInt16LE(0x12) as BackgroundCode, // Swapped on load
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ export function createProfile(buf: Buffer): CreateProfileRequest {
|
||||
),
|
||||
field_0034: buf.readUInt32LE(0x0034),
|
||||
car: car(buf.slice(0x0040, 0x00a0)),
|
||||
chara: chara(buf.slice(0x00a0, 0x00b0)),
|
||||
field_00B0: buf.readUInt16LE(0x00b0),
|
||||
field_00B2: buf.readUInt8(0x00b2),
|
||||
chara: chara(buf.slice(0x00a0, 0x00b4)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { AimeId } from "../model/base";
|
||||
import { DiscoverProfileRequest } from "../request/discoverProfile";
|
||||
|
||||
discoverProfile.msgCode = 0x006b as RequestCode;
|
||||
@@ -7,6 +8,6 @@ discoverProfile.msgLen = 0x0010;
|
||||
export function discoverProfile(buf: Buffer): DiscoverProfileRequest {
|
||||
return {
|
||||
type: "discover_profile_req",
|
||||
aimeId: buf.readUInt32LE(0x0004),
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { AimeId } from "../model/base";
|
||||
import { LoadProfileRequest } from "../request/loadProfile";
|
||||
|
||||
loadProfile.msgCode = 0x0067 as RequestCode;
|
||||
@@ -7,7 +8,7 @@ loadProfile.msgLen = 0x0020;
|
||||
export function loadProfile(buf: Buffer): LoadProfileRequest {
|
||||
return {
|
||||
type: "load_profile_req",
|
||||
aimeId: buf.readUInt32LE(0x0004),
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
pcbId: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { Id } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { LoadStockerRequest } from "../request/loadStocker";
|
||||
|
||||
loadStocker.msgCode = 0x00a7 as RequestCode;
|
||||
@@ -7,6 +9,6 @@ loadStocker.msgLen = 0x0010;
|
||||
export function loadStocker(buf: Buffer): LoadStockerRequest {
|
||||
return {
|
||||
type: "load_stocker_req",
|
||||
profileId: buf.readUInt32LE(0x0004),
|
||||
profileId: buf.readUInt32LE(0x0004) as Id<Profile>,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { Id } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { SaveSettingsRequest } from "../request/saveSettings";
|
||||
|
||||
saveSettings.msgCode = 0x00a5 as RequestCode;
|
||||
@@ -11,7 +13,7 @@ export function saveSettings(buf: Buffer): SaveSettingsRequest {
|
||||
|
||||
return {
|
||||
type: "save_settings_req",
|
||||
profileId: buf.readUInt32LE(0x0004),
|
||||
profileId: buf.readUInt32LE(0x0004) as Id<Profile>,
|
||||
dpoint: buf.readUInt32LE(0x0008),
|
||||
settings: {
|
||||
bgMusic: buf.readUInt16LE(0x0002),
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { bitmap } from "./_bitmap";
|
||||
import { chara } from "./_chara";
|
||||
import { RequestCode } from "./_defs";
|
||||
import { BackgroundCode, Id } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { SaveStockerRequest } from "../request/saveStocker";
|
||||
|
||||
saveStocker.msgCode = 0x00a6 as RequestCode;
|
||||
@@ -7,6 +11,8 @@ saveStocker.msgLen = 0x00c0;
|
||||
export function saveStocker(buf: Buffer): SaveStockerRequest {
|
||||
return {
|
||||
type: "save_stocker_req",
|
||||
profileId: buf.readUInt32LE(0x0004),
|
||||
profileId: buf.readUInt32LE(0x0004) as Id<Profile>,
|
||||
backgrounds: bitmap(buf.slice(0x0008, 0x002c)) as BackgroundCode[],
|
||||
chara: chara(buf.slice(0x009e, 0x00b2)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Chara } from "../model/chara";
|
||||
|
||||
export function chara(chara: Chara): Buffer {
|
||||
const buf = Buffer.alloc(0x0010);
|
||||
const buf = Buffer.alloc(0x0014);
|
||||
|
||||
buf.writeUInt8(chara.gender === "male" ? 0 : 1, 0x00);
|
||||
buf.writeUInt16LE(chara.clothing, 0x02);
|
||||
@@ -11,6 +11,8 @@ export function chara(chara: Chara): Buffer {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import { LoadTeamResponse } from "../response/loadTeam";
|
||||
export function _team(res: CreateTeamResponse | LoadTeamResponse) {
|
||||
const buf = Buffer.alloc(0x0ca0);
|
||||
|
||||
iconv.encode(res.name, "shift_jis").copy(buf, 0x0024);
|
||||
buf.writeUInt32LE(res.team.id, 0x000c);
|
||||
iconv.encode(res.team.name, "shift_jis").copy(buf, 0x0024);
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const base = 0x011c + i * 0x004c;
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import iconv = require("iconv-lite");
|
||||
|
||||
import { bitmap } from "./_bitmap";
|
||||
import { car } from "./_car";
|
||||
import { chara } from "./_chara";
|
||||
import { LoadProfileResponse2 } from "../response/loadProfile2";
|
||||
import { bitmap } from "./_bitmap";
|
||||
|
||||
export function loadProfile2(res: LoadProfileResponse2) {
|
||||
// Story stuff
|
||||
//buf.writeUInt32LE(0x1ff, 0x0228);
|
||||
|
||||
// Flairs (bitmap)
|
||||
// buf.fill(0xff, 0x071c, 0x071c + 0xb4);
|
||||
|
||||
const settingsPack =
|
||||
((res.settings.forceQuitEn ? 1 : 0) << 0) |
|
||||
(res.settings.steeringForce << 1) |
|
||||
@@ -33,10 +30,8 @@ export function loadProfile2(res: LoadProfileResponse2) {
|
||||
buf.writeInt32LE(res.fame, 0x0404);
|
||||
iconv.encode(res.name + "\0", "shift_jis").copy(buf, 0x03ee);
|
||||
chara(res.chara).copy(buf, 0x070c);
|
||||
buf.writeUInt16LE(res.background, 0x071c);
|
||||
buf.writeUInt16LE(res.title, 0x071e);
|
||||
bitmap(res.titles, 0xb4).copy(buf, 0x720);
|
||||
buf.writeInt32LE(res.teamId, 0x07e0);
|
||||
buf.writeInt32LE(res.teamId || 0, 0x07e0);
|
||||
car(res.car).copy(buf, 0x0c5c);
|
||||
|
||||
return buf;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Id } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
import { CreateTeamRequest } from "../request/createTeam";
|
||||
import { LoadTeamRequest } from "../request/loadTeam";
|
||||
import { CreateTeamResponse } from "../response/createTeam";
|
||||
@@ -9,7 +11,10 @@ export function _team(
|
||||
req: CreateTeamRequest | LoadTeamRequest
|
||||
): CreateTeamResponse | LoadTeamResponse {
|
||||
const bits = {
|
||||
name: "ASS GENTLEMEN",
|
||||
team: {
|
||||
id: 2 as Id<Team>,
|
||||
name: "ASS GENTLEMEN",
|
||||
},
|
||||
members: [],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,42 @@
|
||||
import { Id } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { Team } from "../model/team";
|
||||
import { CreateProfileRequest } from "../request/createProfile";
|
||||
import { GenericResponse } from "../response/generic";
|
||||
import { World } from "../world";
|
||||
|
||||
export function createProfile(
|
||||
export async function createProfile(
|
||||
w: World,
|
||||
req: CreateProfileRequest
|
||||
): GenericResponse {
|
||||
): Promise<GenericResponse> {
|
||||
const profile: Profile = {
|
||||
id: 1 as Id<Profile>,
|
||||
teamId: 2 as Id<Team>,
|
||||
name: req.name,
|
||||
lv: 1,
|
||||
fame: 0,
|
||||
dpoint: 0,
|
||||
};
|
||||
|
||||
const settings = {
|
||||
bgMusic: 15,
|
||||
forceQuitEn: false,
|
||||
steeringForce: 4,
|
||||
bgVolume: 10,
|
||||
seVolume: 10,
|
||||
cornerGuide: true,
|
||||
lineGuide: false,
|
||||
ghostEn: false,
|
||||
taResultSkip: false,
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
w.profile().save(profile.id, profile),
|
||||
w.chara().save(profile.id, req.chara),
|
||||
w.car().save(profile.id, req.car),
|
||||
w.settings().save(profile.id, settings),
|
||||
]);
|
||||
|
||||
return {
|
||||
type: "generic_res",
|
||||
status: 1,
|
||||
|
||||
@@ -2,12 +2,12 @@ import { DiscoverProfileRequest } from "../request/discoverProfile";
|
||||
import { DiscoverProfileResponse } from "../response/discoverProfile";
|
||||
import { World } from "../world";
|
||||
|
||||
export function discoverProfile(
|
||||
export async function discoverProfile(
|
||||
w: World,
|
||||
req: DiscoverProfileRequest
|
||||
): DiscoverProfileResponse {
|
||||
): Promise<DiscoverProfileResponse> {
|
||||
return {
|
||||
type: "discover_profile_res",
|
||||
exists: true,
|
||||
exists: await w.profile().discoverByAimeId(req.aimeId),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import { Request } from "../request";
|
||||
import { Response } from "../response";
|
||||
import { World } from "../world";
|
||||
|
||||
export function dispatch(w: World, req: Request): Response {
|
||||
export async function dispatch(w: World, req: Request): Promise<Response> {
|
||||
switch (req.type) {
|
||||
case "create_profile_req":
|
||||
return createProfile(w, req);
|
||||
|
||||
@@ -2,58 +2,27 @@ import { LoadProfileRequest } from "../request/loadProfile";
|
||||
import { LoadProfileResponse2 } from "../response/loadProfile2";
|
||||
import { World } from "../world";
|
||||
|
||||
export function loadProfile(
|
||||
export async function loadProfile(
|
||||
w: World,
|
||||
req: LoadProfileRequest
|
||||
): LoadProfileResponse2 {
|
||||
): Promise<LoadProfileResponse2> {
|
||||
const profile = await w.profile().loadByAimeId(req.aimeId);
|
||||
const settings = await w.settings().load(profile.id);
|
||||
const chara = await w.chara().load(profile.id);
|
||||
const titles = await w.titles().load(profile.id);
|
||||
const car = await w.car().load(profile.id);
|
||||
|
||||
return {
|
||||
type: "load_profile_v2_res",
|
||||
name: "てすと",
|
||||
profileId: 0x11223344,
|
||||
lv: 99,
|
||||
fame: 1234,
|
||||
dpoint: 54321,
|
||||
teamId: 0x22334455,
|
||||
settings: {
|
||||
bgMusic: 15,
|
||||
forceQuitEn: false,
|
||||
steeringForce: 4,
|
||||
bgVolume: 10,
|
||||
seVolume: 10,
|
||||
cornerGuide: true,
|
||||
lineGuide: false,
|
||||
ghostEn: false,
|
||||
taResultSkip: false,
|
||||
},
|
||||
chara: {
|
||||
gender: "male",
|
||||
clothing: 0,
|
||||
mouth: 0,
|
||||
field_06: 0,
|
||||
field_08: 0,
|
||||
field_0A: 0,
|
||||
field_0C: 0,
|
||||
field_0E: 0,
|
||||
},
|
||||
background: 0,
|
||||
title: 0,
|
||||
titles: [],
|
||||
car: {
|
||||
field_00: 0,
|
||||
field_02: 0,
|
||||
field_04: [],
|
||||
model: 0,
|
||||
color: 0,
|
||||
transmission: 0,
|
||||
field_4A: 0,
|
||||
field_4C: 21527,
|
||||
field_50_lo: 3674393,
|
||||
field_50_hi: 6407,
|
||||
field_58: 0,
|
||||
field_5A: 0,
|
||||
field_5B: 0,
|
||||
field_5C: 1,
|
||||
field_5E: 0,
|
||||
},
|
||||
name: profile.name,
|
||||
profileId: profile.id,
|
||||
lv: profile.lv,
|
||||
fame: profile.fame,
|
||||
dpoint: profile.dpoint,
|
||||
teamId: profile.teamId,
|
||||
settings,
|
||||
chara,
|
||||
titles,
|
||||
car,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,13 +2,15 @@ import { LoadStockerRequest } from "../request/loadStocker";
|
||||
import { LoadStockerResponse } from "../response/loadStocker";
|
||||
import { World } from "../world";
|
||||
|
||||
export function loadStocker(
|
||||
export async function loadStocker(
|
||||
w: World,
|
||||
req: LoadStockerRequest
|
||||
): LoadStockerResponse {
|
||||
): Promise<LoadStockerResponse> {
|
||||
const backgrounds = await w.backgrounds().load(req.profileId);
|
||||
|
||||
return {
|
||||
type: "load_stocker_res",
|
||||
status: 1,
|
||||
backgrounds: [],
|
||||
backgrounds,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ import { SaveSettingsRequest } from "../request/saveSettings";
|
||||
import { GenericResponse } from "../response/generic";
|
||||
import { World } from "../world";
|
||||
|
||||
export function saveSettings(
|
||||
export async function saveSettings(
|
||||
w: World,
|
||||
req: SaveSettingsRequest
|
||||
): GenericResponse {
|
||||
): Promise<GenericResponse> {
|
||||
await w.settings().save(req.profileId, req.settings);
|
||||
|
||||
return {
|
||||
type: "generic_res",
|
||||
status: 1, // ignored but whatever
|
||||
|
||||
@@ -2,10 +2,13 @@ import { SaveStockerRequest } from "../request/saveStocker";
|
||||
import { GenericResponse } from "../response/generic";
|
||||
import { World } from "../world";
|
||||
|
||||
export function saveStocker(
|
||||
export async function saveStocker(
|
||||
w: World,
|
||||
req: SaveStockerRequest
|
||||
): GenericResponse {
|
||||
): Promise<GenericResponse> {
|
||||
await w.backgrounds().save(req.profileId, req.backgrounds);
|
||||
await w.chara().save(req.profileId, req.chara);
|
||||
|
||||
return {
|
||||
type: "generic_res",
|
||||
status: 1, // ignored
|
||||
|
||||
+3
-4
@@ -2,18 +2,17 @@ import { Socket } from "net";
|
||||
|
||||
import { dispatch } from "./handler";
|
||||
import { setup } from "./setup";
|
||||
import { World } from "./world";
|
||||
|
||||
const world = new World();
|
||||
import { createWorld } from "./world";
|
||||
|
||||
export default async function idz(socket: Socket) {
|
||||
const world = await createWorld("./state");
|
||||
const { input, output } = setup(socket);
|
||||
|
||||
console.log("Idz: Connection opened");
|
||||
|
||||
try {
|
||||
for await (const req of input) {
|
||||
output.write(dispatch(world, req));
|
||||
output.write(await dispatch(world, req));
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Idz: Error", e);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// Quasi-nominative "brands". These double-underscore properties never actually
|
||||
// exist at run time, we just pretend they do.
|
||||
|
||||
export type Id<T> = number & { __id: T };
|
||||
export type AimeId = number & { __brand: "aime_id" };
|
||||
export type BackgroundCode = number & { __brand: "background_code" };
|
||||
export type TitleCode = number & { __brand: "title_code" };
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BackgroundCode, TitleCode } from "./base";
|
||||
|
||||
export interface Chara {
|
||||
gender: "male" | "female";
|
||||
clothing: number;
|
||||
@@ -7,4 +9,6 @@ export interface Chara {
|
||||
field_0A: number;
|
||||
field_0C: number;
|
||||
field_0E: number;
|
||||
title: TitleCode;
|
||||
background: BackgroundCode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export { AimeId, BackgroundCode, Id, TitleCode } from "./base";
|
||||
export { Car } from "./car";
|
||||
export { Chara } from "./chara";
|
||||
export { Profile } from "./profile";
|
||||
export { Settings } from "./settings";
|
||||
export { Team } from "./team";
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Id } from "./base";
|
||||
import { Team } from "./team";
|
||||
|
||||
export interface Profile {
|
||||
id: Id<Profile>;
|
||||
teamId?: Id<Team>;
|
||||
name: string;
|
||||
lv: number;
|
||||
fame: number;
|
||||
dpoint: number;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Id } from "./base";
|
||||
|
||||
export interface Team {
|
||||
id: Id<Team>;
|
||||
name: string;
|
||||
}
|
||||
@@ -9,6 +9,4 @@ export interface CreateProfileRequest {
|
||||
field_0034: number;
|
||||
car: Car;
|
||||
chara: Chara;
|
||||
field_00B0: number;
|
||||
field_00B2: number;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { AimeId } from "../model";
|
||||
|
||||
export interface DiscoverProfileRequest {
|
||||
type: "discover_profile_req";
|
||||
aimeId: number;
|
||||
aimeId: AimeId;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { AimeId } from "../model/base";
|
||||
|
||||
export interface LoadProfileRequest {
|
||||
type: "load_profile_req";
|
||||
aimeId: number;
|
||||
aimeId: AimeId;
|
||||
pcbId: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Id } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
|
||||
export interface LoadStockerRequest {
|
||||
type: "load_stocker_req";
|
||||
profileId: number;
|
||||
profileId: Id<Profile>;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Id } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { Settings } from "../model/settings";
|
||||
|
||||
export interface SaveSettingsRequest {
|
||||
type: "save_settings_req";
|
||||
profileId: number;
|
||||
profileId: Id<Profile>;
|
||||
dpoint: number; // ?? why
|
||||
settings: Settings;
|
||||
field_0010: number;
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { BackgroundCode, Id } from "../model/base";
|
||||
import { Chara } from "../model/chara";
|
||||
import { Profile } from "../model/profile";
|
||||
|
||||
export interface SaveStockerRequest {
|
||||
type: "save_stocker_req";
|
||||
profileId: number;
|
||||
profileId: Id<Profile>;
|
||||
backgrounds: BackgroundCode[];
|
||||
chara: Chara;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Team } from "../model/team";
|
||||
|
||||
export interface TeamMember {
|
||||
name: string;
|
||||
lv: number;
|
||||
@@ -5,7 +7,7 @@ export interface TeamMember {
|
||||
}
|
||||
|
||||
export interface BaseTeamResponse {
|
||||
name: string;
|
||||
team: Team;
|
||||
members: TeamMember[];
|
||||
// giga TODO
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Id } from "../model/base";
|
||||
import { Car } from "../model/car";
|
||||
import { Chara } from "../model/chara";
|
||||
import { Profile } from "../model/profile";
|
||||
import { Settings } from "../model/settings";
|
||||
|
||||
export interface LoadProfileResponse2 {
|
||||
type: "load_profile_v2_res";
|
||||
name: string;
|
||||
profileId: number;
|
||||
profileId: Id<Profile>;
|
||||
lv: number;
|
||||
fame: number;
|
||||
dpoint: number;
|
||||
teamId: number;
|
||||
teamId?: number;
|
||||
settings: Settings;
|
||||
chara: Chara;
|
||||
background: number;
|
||||
title: number;
|
||||
titles: number[];
|
||||
car: Car;
|
||||
// giga TODO
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export class World {
|
||||
// Hand out references to some sort of backing store Repository objects here.
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import * as fs from "fs";
|
||||
import { promisify } from "util";
|
||||
|
||||
import { Repository } from "./base";
|
||||
import { Id } from "../model/base";
|
||||
|
||||
const exists = promisify(fs.exists);
|
||||
const readFile = promisify(fs.readFile);
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
|
||||
export class SingletonJsonRepo<T, K = T> implements Repository<T, K> {
|
||||
private _path: string;
|
||||
|
||||
constructor(root: string, name: string) {
|
||||
this._path = `${root}/${name}.json`;
|
||||
}
|
||||
|
||||
discover(id: Id<K>): Promise<boolean> {
|
||||
return this._discoverSingleton();
|
||||
}
|
||||
|
||||
load(id: Id<K>): Promise<T> {
|
||||
return this._loadSingleton();
|
||||
}
|
||||
|
||||
save(id: Id<K>, obj: T) {
|
||||
console.log("Idz: Repository: Saving ", this._path);
|
||||
|
||||
const json = JSON.stringify(obj, undefined, 2);
|
||||
|
||||
return writeFile(this._path, json, { encoding: "utf8" });
|
||||
}
|
||||
|
||||
protected _discoverSingleton(): Promise<boolean> {
|
||||
return exists(this._path);
|
||||
}
|
||||
|
||||
protected async _loadSingleton(): Promise<T> {
|
||||
console.log("Idz: Repository: Loading ", this._path);
|
||||
|
||||
const json = await readFile(this._path, { encoding: "utf8" });
|
||||
|
||||
return JSON.parse(json);
|
||||
}
|
||||
}
|
||||
|
||||
export class SingletonTextRepo<T extends number[], K = T>
|
||||
implements Repository<T, K> {
|
||||
private _path: string;
|
||||
|
||||
constructor(root: string, name: string) {
|
||||
this._path = `${root}/${name}.txt`;
|
||||
}
|
||||
|
||||
discover(id: Id<K>): Promise<boolean> {
|
||||
return exists(this._path);
|
||||
}
|
||||
|
||||
async load(id: Id<K>): Promise<T> {
|
||||
console.log("Idz: Repository: Loading ", this._path);
|
||||
|
||||
const existence = await exists(this._path);
|
||||
|
||||
if (!existence) {
|
||||
return new Array() as T;
|
||||
}
|
||||
|
||||
const text = await readFile(this._path, { encoding: "utf8" });
|
||||
const lines = text.split("\n");
|
||||
const codes = lines.map(parseInt);
|
||||
|
||||
return codes as T;
|
||||
}
|
||||
|
||||
save(id: Id<K>, codes: T): Promise<void> {
|
||||
console.log("Idz: Repository: Saving ", this._path);
|
||||
|
||||
const text = codes.map(code => code.toString()).join("\n") + "\n";
|
||||
|
||||
return writeFile(this._path, text, { encoding: "utf8" });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import * as Model from "../model";
|
||||
|
||||
export interface Repository<T, K = T> {
|
||||
discover(id: Model.Id<K>): Promise<boolean>;
|
||||
|
||||
load(id: Model.Id<K>): Promise<T>;
|
||||
|
||||
save(id: Model.Id<K>, obj: T): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ProfileRepository extends Repository<Model.Profile> {
|
||||
discoverByAimeId(id: Model.AimeId): Promise<boolean>;
|
||||
|
||||
loadByAimeId(id: Model.AimeId): Promise<Model.Profile>;
|
||||
}
|
||||
|
||||
export interface World {
|
||||
backgrounds(): Repository<Model.BackgroundCode[], Model.Profile>;
|
||||
|
||||
car(): Repository<Model.Car, Model.Profile>;
|
||||
|
||||
chara(): Repository<Model.Chara, Model.Profile>;
|
||||
|
||||
profile(): ProfileRepository;
|
||||
|
||||
settings(): Repository<Model.Settings, Model.Profile>;
|
||||
|
||||
titles(): Repository<Model.TitleCode[], Model.Profile>;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import * as fs from "fs";
|
||||
import { promisify } from "util";
|
||||
|
||||
import { World } from "./base";
|
||||
import { ProfileRepositoryImpl } from "./profile";
|
||||
import { SingletonJsonRepo, SingletonTextRepo } from "./_singleton";
|
||||
import * as Model from "../model";
|
||||
|
||||
export { World };
|
||||
|
||||
const exists = promisify(fs.exists);
|
||||
const mkdir = promisify(fs.mkdir);
|
||||
|
||||
class WorldImpl implements World {
|
||||
constructor(private readonly _root: string) {}
|
||||
|
||||
backgrounds() {
|
||||
return new SingletonTextRepo<Model.BackgroundCode[], Model.Profile>(
|
||||
this._root,
|
||||
"backgrounds"
|
||||
);
|
||||
}
|
||||
|
||||
car() {
|
||||
return new SingletonJsonRepo<Model.Car, Model.Profile>(this._root, "car");
|
||||
}
|
||||
|
||||
chara() {
|
||||
return new SingletonJsonRepo<Model.Chara, Model.Profile>(
|
||||
this._root,
|
||||
"chara"
|
||||
);
|
||||
}
|
||||
|
||||
profile() {
|
||||
return new ProfileRepositoryImpl(this._root);
|
||||
}
|
||||
|
||||
settings() {
|
||||
return new SingletonJsonRepo<Model.Settings, Model.Profile>(
|
||||
this._root,
|
||||
"settings"
|
||||
);
|
||||
}
|
||||
|
||||
titles() {
|
||||
return new SingletonTextRepo<Model.TitleCode[], Model.Profile>(
|
||||
this._root,
|
||||
"titles"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createWorld(root: string): Promise<World> {
|
||||
const doesExist = await exists(root);
|
||||
|
||||
if (!doesExist) {
|
||||
await mkdir(root);
|
||||
}
|
||||
|
||||
return new WorldImpl(root);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { SingletonJsonRepo } from "./_singleton";
|
||||
import { ProfileRepository } from "./base";
|
||||
import { AimeId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
|
||||
export class ProfileRepositoryImpl extends SingletonJsonRepo<Profile>
|
||||
implements ProfileRepository {
|
||||
constructor(root) {
|
||||
super(root, "profile");
|
||||
}
|
||||
|
||||
discoverByAimeId(id: AimeId): Promise<boolean> {
|
||||
return this._discoverSingleton();
|
||||
}
|
||||
|
||||
loadByAimeId(id: AimeId): Promise<Profile> {
|
||||
return this._loadSingleton();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user