diff --git a/plugins/minimap/controller.ts b/plugins/minimap/controller.ts index 7d35fd4d..9758cd73 100644 --- a/plugins/minimap/controller.ts +++ b/plugins/minimap/controller.ts @@ -26,6 +26,8 @@ import { extractChunkFromTile, renderTileToPixels, pixelsToRGBA, + parseChunkDataRecord, + parsePixelDataRecord, parseRecipeTileBinary, type ParsedRecipeTile, } from "./utils/tile-utils"; @@ -277,11 +279,8 @@ export class ControllerPlugin extends BaseControllerPlugin { private async deleteFilesMatching(dir: string, predicate: (file: string) => boolean) { const files = await fs.readdir(dir); - for (const file of files) { - if (predicate(file)) { - await fs.remove(path.join(dir, file)); - } - } + const matches = files.filter(file => predicate(file)); + await Promise.all(matches.map(file => fs.remove(path.join(dir, file)))); } private removeSurfaceQueueEntries(instanceId: number, surface: string, force?: string) { @@ -659,7 +658,7 @@ export class ControllerPlugin extends BaseControllerPlugin { existingTile: Buffer, newChunks: Map ): Promise { - const existingChunks = new Map(); + const existingChunks = new Map(); // Parse existing tile data to find existing chunks let offset = 0; @@ -674,14 +673,20 @@ export class ControllerPlugin extends BaseControllerPlugin { offset += 1; if (type === 1) { // Chunk - const parseResult = this.parseChunkData(existingTile, offset, existingChunks); + const parseResult = parseChunkDataRecord(existingTile, offset); if (!parseResult.success) { + this.logger.error(`Invalid tile data: insufficient chunk data at offset ${parseResult.newOffset}`); break; } + existingChunks.set( + `${parseResult.record.chunkX}_${parseResult.record.chunkY}`, + { tick: parseResult.record.tick, data: parseResult.record.data } + ); offset = parseResult.newOffset; } else if (type === 2) { // Pixels - const parseResult = this.parsePixelData(existingTile, offset); + const parseResult = parsePixelDataRecord(existingTile, offset); if (!parseResult.success) { + this.logger.error(`Invalid tile data: insufficient pixel data at offset ${parseResult.newOffset}`); break; } offset = parseResult.newOffset; @@ -748,75 +753,6 @@ export class ControllerPlugin extends BaseControllerPlugin { return Buffer.concat([existingTile, ...appendBuffers]); } - private parseChunkData( - existingTile: Buffer, - offset: number, - existingChunks: Map - ): { success: boolean, newOffset: number } { - if (offset + 7 > existingTile.length) { - this.logger.error(`Invalid tile data: insufficient chunk header data at offset ${offset}`); - return { success: false, newOffset: offset }; - } - - // tick_sec (4 bytes): game second (uint32) - const tick = existingTile.readUInt32BE(offset); - offset += 4; - // chunk_coords (1 byte): packed nibble coords (x<<4 | y) - const chunkCoordsByte = existingTile.readUInt8(offset); - offset += 1; - // length (2 bytes): number of chunk bytes that follow (uint16) - const length = existingTile.readUInt16BE(offset); - offset += 2; - - if (offset + length > existingTile.length) { - this.logger.error(`Invalid tile data: insufficient chunk data at offset ${offset}`); - return { success: false, newOffset: offset }; - } - - const chunkX = chunkCoordsByte >> 4; - const chunkY = chunkCoordsByte & 0x0F; - const chunkName = `${chunkX}_${chunkY}`; - // data (length bytes): compressed chunk bytes - const data = existingTile.slice(offset, offset + length); - offset += length; - existingChunks.set(chunkName, { tick, data }); - - return { success: true, newOffset: offset }; - } - - private parsePixelData( - existingTile: Buffer, - offset: number - ): { success: boolean, newOffset: number } { - if (offset + 10 > existingTile.length) { - this.logger.error(`Invalid tile data: insufficient pixel header data at offset ${offset}`); - return { success: false, newOffset: offset }; - } - - // tick_sec (4 bytes): game second (uint32) - const tick = existingTile.readUInt32BE(offset); - offset += 4; - // pixel_count (2 bytes): number of pixel coords that follow (uint16) - const pixelCount = existingTile.readUInt16BE(offset); - offset += 2; - // new_color (2 bytes): RGB565 (uint16) - // old_color (2 bytes): RGB565 (uint16) - offset += 2 + 2; - void tick; // parsed for validation/advancing offset; not needed here - const pixelDataLength = pixelCount * 2; - - if (offset + pixelDataLength > existingTile.length) { - this.logger.error(`Invalid tile data: insufficient pixel data at offset ${offset}`); - return { success: false, newOffset: offset }; - } - - // pixel_data (pixelCount * 2 bytes): x (1 byte), y (1 byte) pairs - // Skip payload since we only need offsets for chunk discovery. - offset += pixelDataLength; - - return { success: true, newOffset: offset }; - } - async loadExistingTagContent() { try { // Use existsSync for synchronous directory existence check diff --git a/plugins/minimap/utils/tile-utils.ts b/plugins/minimap/utils/tile-utils.ts index c1c5487f..d64f83e0 100644 --- a/plugins/minimap/utils/tile-utils.ts +++ b/plugins/minimap/utils/tile-utils.ts @@ -433,6 +433,90 @@ export function parseTileData(tileData: BinaryData): ParsedTileData { }; } +export interface ParsedChunkDataRecord { + tick: number; + chunkX: number; + chunkY: number; + data: BinaryData; +} + +export interface ParsedPixelDataRecord { + tick: number; + pixelCount: number; + newColor: number; + oldColor: number; + pixelDataOffset: number; +} + +export type ParseRecordResult = + | { success: false; newOffset: number } + | { success: true; newOffset: number; record: T }; + +export function parseChunkDataRecord( + tileData: BinaryData, + offset: number +): ParseRecordResult { + if (offset + 7 > tileData.length) { + return { success: false, newOffset: offset }; + } + + const tick = readUInt32BE(tileData, offset); + offset += 4; + const chunkCoordsByte = readUInt8(tileData, offset); + offset += 1; + const length = readUInt16BE(tileData, offset); + offset += 2; + + if (offset + length > tileData.length) { + return { success: false, newOffset: offset }; + } + + const chunkX = chunkCoordsByte >> 4; + const chunkY = chunkCoordsByte & 0x0F; + const data = sliceData(tileData, offset, offset + length); + + return { + success: true, + newOffset: offset + length, + record: { tick, chunkX, chunkY, data }, + }; +} + +export function parsePixelDataRecord( + tileData: BinaryData, + offset: number +): ParseRecordResult { + if (offset + 10 > tileData.length) { + return { success: false, newOffset: offset }; + } + + const tick = readUInt32BE(tileData, offset); + offset += 4; + const pixelCount = readUInt16BE(tileData, offset); + offset += 2; + const newColor = readUInt16BE(tileData, offset); + offset += 2; + const oldColor = readUInt16BE(tileData, offset); + offset += 2; + + const pixelDataLength = pixelCount * 2; + if (offset + pixelDataLength > tileData.length) { + return { success: false, newOffset: offset }; + } + + return { + success: true, + newOffset: offset + pixelDataLength, + record: { + tick, + pixelCount, + newColor, + oldColor, + pixelDataOffset: offset, + }, + }; +} + /** * Parse chunk record from tile data * @param tileData The binary tile data being processed @@ -447,35 +531,22 @@ function parseChunkRecord( chunks: ChunkRecord[], ticks: Set ): { success: boolean; newOffset: number } { - if (offset + 7 > tileData.length) { - return { success: false, newOffset: offset }; + const parsed = parseChunkDataRecord(tileData, offset); + if (!parsed.success) { + return parsed; } - const tick = readUInt32BE(tileData, offset) * 60; // Convert back to actual tick + const tick = parsed.record.tick * 60; // Convert back to actual tick ticks.add(tick); - offset += 4; - - const chunkCoordsByte = readUInt8(tileData, offset); - offset += 1; - const length = readUInt16BE(tileData, offset); - offset += 2; - - if (offset + length > tileData.length) { - return { success: false, newOffset: offset }; - } - - const chunkX = chunkCoordsByte >> 4; - const chunkY = chunkCoordsByte & 0x0F; - const data = sliceData(tileData, offset, offset + length); chunks.push({ tick, - chunkX, - chunkY, - data, + chunkX: parsed.record.chunkX, + chunkY: parsed.record.chunkY, + data: parsed.record.data, }); - return { success: true, newOffset: offset + length }; + return { success: true, newOffset: parsed.newOffset }; } /** @@ -492,35 +563,24 @@ function parsePixelChangeRecord( pixelChanges: PixelChangeRecord[], ticks: Set ): { success: boolean; newOffset: number } { - if (offset + 10 > tileData.length) { - return { success: false, newOffset: offset }; + const parsed = parsePixelDataRecord(tileData, offset); + if (!parsed.success) { + return parsed; } - const tick = readUInt32BE(tileData, offset) * 60; // Convert back to actual tick + const tick = parsed.record.tick * 60; // Convert back to actual tick ticks.add(tick); - offset += 4; - const pixelCount = readUInt16BE(tileData, offset); - offset += 2; - const newColor = readUInt16BE(tileData, offset); - offset += 2; - const oldColor = readUInt16BE(tileData, offset); - offset += 2; - - const expectedDataLength = pixelCount * 2; - if (offset + expectedDataLength > tileData.length) { - return { success: false, newOffset: offset }; - } const changes: PixelChange[] = []; - for (let i = 0; i < pixelCount; i++) { - const pixelOffset = offset + i * 2; + for (let i = 0; i < parsed.record.pixelCount; i++) { + const pixelOffset = parsed.record.pixelDataOffset + i * 2; const x = readUInt8(tileData, pixelOffset); const y = readUInt8(tileData, pixelOffset + 1); - changes.push({ x, y, newColor, oldColor }); + changes.push({ x, y, newColor: parsed.record.newColor, oldColor: parsed.record.oldColor }); } pixelChanges.push({ tick, changes }); - return { success: true, newOffset: offset + expectedDataLength }; + return { success: true, newOffset: parsed.newOffset }; } /**