diff --git a/package-lock.json b/package-lock.json index 082195b..287c145 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cdb-converter", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cdb-converter", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "dependencies": { "@types/sql.js": "^1.4.11", diff --git a/package.json b/package.json index 6ab12ac..88898a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cdb-converter", - "version": "0.2.0", + "version": "0.3.0", "description": "Convert Pro Cycling Manager CDB files to/from SQLite and other formats. TypeScript library with zero configuration.", "license": "MIT", "author": "mpicciolli", diff --git a/src/reader.ts b/src/reader.ts index a222dca..6e22f85 100644 --- a/src/reader.ts +++ b/src/reader.ts @@ -248,9 +248,24 @@ export class CDBReader { break; default: - throw new Error( - `Unknown chunk type: 0x${(header.chunkType as number).toString(16)}`, - ); + { + if (typeof console !== "undefined") { + console.warn( + `Skipping unknown chunk type: 0x${(header.chunkType as number).toString(16)} at position ${chunkStartPos}`, + ); + } + const skippedBytes = chunkEndPos - this.pos - 4; + if (skippedBytes < 0) { + throw new Error( + `Invalid chunk size for unknown chunk type 0x${(header.chunkType as number).toString(16)} at position ${chunkStartPos}`, + ); + } + result = { + type: header.chunkType, + value: this.readBytes(skippedBytes), + }; + } + break; } this.readPadding(); diff --git a/test/reader.test.ts b/test/reader.test.ts index 6d613b0..9e1322e 100644 --- a/test/reader.test.ts +++ b/test/reader.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { CDBReader } from "../src/reader"; import { CHUNK_TYPE, DATA_TYPE, MAGIC } from "../src/tableMetadata"; import { CDBWriter } from "../src/writer"; @@ -59,6 +59,22 @@ function createWrapperWithMissingColumnDescription(): Uint8Array { return writer.getData(); } +function createUnknownChunkBuffer(payloadLength: number): Uint8Array { + const chunkSize = 28 + payloadLength; + const buffer = new ArrayBuffer(chunkSize); + const view = new DataView(buffer); + + view.setUint32(0, MAGIC.CHUNK_BEGIN, true); + view.setUint32(4, chunkSize, true); + view.setUint32(8, 0x99, true); // unknown chunk type + view.setUint32(12, 0, true); + view.setUint32(16, 0, true); + view.setUint32(20, MAGIC.CHUNK_SEPARATOR, true); + view.setUint32(24 + payloadLength, MAGIC.CHUNK_END, true); + + return new Uint8Array(buffer); +} + function createFloatListTable(rows: string[]): Uint8Array { const writer = new CDBWriter(); @@ -134,6 +150,39 @@ describe("CDBReader", () => { ); }); + it("skips an unknown chunk type instead of throwing", () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + try { + const reader = new CDBReader(createUnknownChunkBuffer(8)); + + const chunk = reader.readChunk(); + + expect(chunk.type).toBe(0x99); + expect(chunk.value).toBeInstanceOf(Uint8Array); + expect((chunk.value as Uint8Array).length).toBe(8); + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining("Skipping unknown chunk type: 0x99"), + ); + } finally { + warnSpy.mockRestore(); + } + }); + + it("throws when an unknown chunk declares an impossibly small size", () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + try { + const buffer = createUnknownChunkBuffer(0); + new DataView(buffer.buffer).setUint32(4, 4, true); + const reader = new CDBReader(buffer); + + expect(() => reader.readChunk()).toThrowError( + "Invalid chunk size for unknown chunk type 0x99 at position 0", + ); + } finally { + warnSpy.mockRestore(); + } + }); + describe("FLOAT_LIST formatting", () => { it("round-trips small magnitude values without losing precision", () => { const original = Math.fround(1e-8);