Skip to content

Commit

Permalink
feat(clientdb): add support for reading localized strings in db records
Browse files Browse the repository at this point in the history
  • Loading branch information
fallenoak committed Jan 14, 2024
1 parent 35adb0f commit 4d31520
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 9 deletions.
Binary file added fixture/clientdb/AreaTable.dbc
Binary file not shown.
25 changes: 23 additions & 2 deletions src/lib/clientdb/ClientDb.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IoSource, openStream } from '@wowserhq/io';
import { IoSource, IoStream, openStream } from '@wowserhq/io';
import ClientDbRecord from './ClientDbRecord.js';
import * as dbIo from './io.js';

Expand Down Expand Up @@ -29,14 +29,15 @@ class ClientDb<T extends ClientDbRecord> {
const stream = openStream(source);

const header = dbIo.header.read(stream);
const stringBlockStream = this.#readStringBlock(stream, header);

this.#recordCount = header.recordCount;

const records = new Array(this.#recordCount);
const recordsById = {};

for (let i = 0; i < this.#recordCount; i++) {
const record = new this.#RecordClass().load(stream);
const record = new this.#RecordClass().load(stream, stringBlockStream);

this.#minId = record.id < this.#minId ? record.id : this.#minId;
this.#maxId = record.id > this.#maxId ? record.id : this.#maxId;
Expand All @@ -53,6 +54,26 @@ class ClientDb<T extends ClientDbRecord> {
return this;
}

#readStringBlock(stream: IoStream, header: any) {
const recordsStart = stream.offset;
const recordsSize = header.recordCount * header.recordSize;

const stringBlockStart = recordsStart + recordsSize;
const stringBlockSize = header.stringBlockSize;

// Save offset
const offset = stream.offset;

stream.offset = stringBlockStart;
const stringBlock = stream.readBytes(stringBlockSize);
const stringBlockStream = openStream(stringBlock);

// Restore offset
stream.offset = offset;

return stringBlockStream;
}

getRecord(id: number): T {
if (id < this.#minId || id > this.#maxId) {
return null;
Expand Down
34 changes: 29 additions & 5 deletions src/lib/clientdb/ClientDbRecord.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IoSource, IoType, openStream } from '@wowserhq/io';
import { IoStream, IoType } from '@wowserhq/io';
import * as dbIo from './io.js';

class ClientDbRecord {
id: number;
Expand All @@ -9,17 +10,40 @@ class ClientDbRecord {
this.#recordIo = recordIo;
}

load(source: IoSource) {
const stream = openStream(source);

load(stream: IoStream, stringBlock: IoStream) {
const record = this.#recordIo.read(stream);

for (const [key, value] of Object.entries(record)) {
this[key] = value;
if (key.endsWith('.locstring')) {
const strings = this.#readLocString(value as number[], stringBlock);
const normalizedKey = key.replace('.locstring', '');

this[normalizedKey] = strings;
} else {
this[key] = value;
}
}

return this;
}

#readLocString(value: number[], stringBlock: IoStream) {
// All values before last are offsets in the string block
const offsets = value.slice(0, -1);

// Last value is flags
const flags = value.at(-1);

const strings: string[] = [];

for (const offset of offsets) {
stringBlock.offset = offset;
const string = dbIo.string.read(stringBlock);
strings.push(string);
}

return strings;
}
}

export default ClientDbRecord;
Expand Down
4 changes: 3 additions & 1 deletion src/lib/clientdb/io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ const header: IoType = io.struct({
stringBlockSize: io.uint32le,
});

export { header };
const string: IoType = io.string({ terminate: true });

export { header, string };
48 changes: 48 additions & 0 deletions src/lib/clientdb/record/AreaTableRecord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as io from '@wowserhq/io';
import ClientDbRecord from '../ClientDbRecord.js';

const recordIo = io.struct({
id: io.int32le,
continentId: io.int32le,
parentAreaId: io.int32le,
areaBit: io.int32le,
flags: io.int32le,
soundProviderPref: io.int32le,
soundProviderPrefUnderwater: io.int32le,
ambienceId: io.int32le,
zoneMusic: io.int32le,
introSound: io.int32le,
explorationLevel: io.int32le,
'areaName.locstring': io.array(io.uint32le, { size: 17 }),
factionGroupMask: io.int32le,
liquidTypeId: io.array(io.int32le, { size: 4 }),
minElevation: io.float32le,
ambientMultiplier: io.float32le,
lightId: io.int32le,
});

class AreaTableRecord extends ClientDbRecord {
continentId: number;
parentAreaId: number;
areaBit: number;
flags: number;
soundProviderPref: number;
soundProviderPrefUnderwater: number;
ambienceId: number;
zoneMusic: number;
introSound: number;
explorationLevel: number;
areaName: string[];
factionGroupMask: number;
liquidTypeId: number[];
minElevation: number;
ambientMultiplier: number;
lightId: number;

constructor() {
super(recordIo);
}
}

export default AreaTableRecord;
export { AreaTableRecord };
12 changes: 11 additions & 1 deletion src/spec/clientdb/ClientDb.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { describe, expect, test } from 'vitest';
import ClientDb from '../../lib/clientdb/ClientDb.js';
import LightRecord from '../../lib/clientdb/record/LightRecord.js';
import AreaTableRecord from '../../lib/clientdb/record/AreaTableRecord.js';

describe('ClientDb', () => {
describe('load', () => {
test('should load blp from valid file', () => {
test('should load dbc from valid file', () => {
const dbc = new ClientDb(LightRecord).load('./fixture/clientdb/Light.dbc');
const record = dbc.getRecordByIndex(1);

expect(record.id).toBe(2);
expect(record.lightParamsId).toEqual([14, 15, 494, 15, 4, 0, 0, 0]);
});

test('should load dbc with localized strings from valid file', () => {
const dbc = new ClientDb(AreaTableRecord).load('./fixture/clientdb/AreaTable.dbc');
const record = dbc.getRecordByIndex(1);

expect(record.id).toBe(2);
expect(record.areaName[0]).toBe('Longshore');
expect(record.minElevation).toBe(-500);
});
});
});

0 comments on commit 4d31520

Please sign in to comment.