UNPKG

@runejs/filestore

Version:

Tools for managing the RuneJS filestore.

542 lines (541 loc) 19.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WidgetStore = exports.LineWidget = exports.TooltipWidget = exports.StaticItemWidget = exports.ModelWidget = exports.SpriteWidget = exports.LinkWidget = exports.RectangleWidget = exports.InteractableItemWidget = exports.TextWidget = exports.ContainerWidget = exports.ParentWidget = exports.WidgetBase = void 0; const common_1 = require("@runejs/common"); const node_fs_1 = require("node:fs"); const common_2 = require("@runejs/common"); class WidgetBase { id; parentId; type; format; originalX; originalY; x; y; width; height; menuType; contentType; opacity; hidden; targetVerb; spellName; clickMask; hintText; hoveredSiblingId; alternateOperators; alternateRhs; cs1; hasListeners; /** * Writes this unpacked widget file to the disk under `./unpacked/widgets/{widgetId}_widget.json` */ async writeToDisk() { return new Promise((resolve, reject) => { try { if (!(0, node_fs_1.existsSync)('./unpacked/widgets')) { (0, node_fs_1.mkdirSync)('./unpacked/widgets'); } const { id } = this; (0, node_fs_1.writeFileSync)(`./unpacked/widgets/${id}.json`, JSON.stringify(this, null, 4)); resolve(); } catch (error) { reject(error); } }); } } exports.WidgetBase = WidgetBase; class ParentWidget extends WidgetBase { children; constructor(id) { super(); this.id = id; } } exports.ParentWidget = ParentWidget; class ContainerWidget extends WidgetBase { type = 0; scrollHeight; scrollPosition; scrollWidth; children; } exports.ContainerWidget = ContainerWidget; class TextWidget extends WidgetBase { type = 1; textAlignmentX; textAlignmentY; lineHeight; fontId; textShadowed; textColor; } exports.TextWidget = TextWidget; class InteractableItemWidget extends WidgetBase { type = 2; items; itemAmounts; itemSwapable; isInventory; itemUsable; itemDeletesDraged; itemSpritePadsX; itemSpritePadsY; imageX; imageY; images; options; } exports.InteractableItemWidget = InteractableItemWidget; class RectangleWidget extends WidgetBase { type = 3; filled; textColor; alternateTextColor; hoveredTextColor; alternateHoveredTextColor; } exports.RectangleWidget = RectangleWidget; class LinkWidget extends WidgetBase { type = 4; textAlignmentX; textAlignmentY; lineHeight; fontId; textShadowed; text; alternateText; textColor; alternateTextColor; hoveredTextColor; alternateHoveredTextColor; } exports.LinkWidget = LinkWidget; class SpriteWidget extends WidgetBase { type = 5; spriteId; alternateSpriteId; textureId; tiled; } exports.SpriteWidget = SpriteWidget; class ModelWidget extends WidgetBase { type = 6; modelType; modelId; alternateModelType; alternateModelId; animation; alternateAnimation; modelZoom; rotationX; rotationY; rotationZ; offsetX; offsetY; orthogonal; } exports.ModelWidget = ModelWidget; class StaticItemWidget extends WidgetBase { type = 7; items; itemAmounts; isInventory; itemSpritePadsX; itemSpritePadsY; options; textAlignmentX; fontId; textShadowed; textColor; } exports.StaticItemWidget = StaticItemWidget; class TooltipWidget extends WidgetBase { type = 8; text; } exports.TooltipWidget = TooltipWidget; class LineWidget extends WidgetBase { type = 9; lineWidth; textColor; } exports.LineWidget = LineWidget; /** * Controls game interface widget file format and storage. */ class WidgetStore { fileStore; /** * The main file index of the widget store. */ widgetFileIndex; constructor(fileStore) { this.fileStore = fileStore; this.widgetFileIndex = fileStore.getIndex('widgets'); } /** * Writes all unpacked widget files to the disk under `./unpacked/widgets/` */ async writeToDisk() { const widgets = this.decodeWidgetStore(); for (const widget of widgets) { try { await widget.writeToDisk(); } catch (e) { common_2.logger.error(e); } } } /** * Decodes the specified widget file. * @param id The numeric ID of the widget file to decode. */ decodeWidget(id) { const file = this.widgetFileIndex.files.get(id); if (file.type === 'file') { return this.decodeWidgetFile(id, file); } if (file.type === 'archive') { const widgetParent = new ParentWidget(id); const archive = file; archive.decodeArchiveFiles(); const widgetChildFiles = Array.from(archive.files.values()); widgetParent.children = new Array(widgetChildFiles.length); for (let i = 0; i < widgetChildFiles.length; i++) { widgetParent.children[i] = this.decodeWidgetFile(i, widgetChildFiles[i]); } return widgetParent; } } /** * Decodes the specified widget file, first determining if it is in the new or older widget format. * @param id The numeric ID of the widget file to decode. * @param widgetFile The file data of the widget file to decode. */ decodeWidgetFile(id, widgetFile) { if (!widgetFile.content) { widgetFile.decompress(); } const content = widgetFile.content; if (content[0] === -1) { return this.decodeWidgetFormat2(id, content); } return this.decodeWidgetFormat1(id, content); } /** * Decodes all widget files within the filestore. * @returns The list of decoded WidgetBase objects from the widget store. */ decodeWidgetStore() { const widgetCount = this.widgetFileIndex.files.size; const widgets = new Array(widgetCount); for (let widgetId = 0; widgetId < widgetCount; widgetId++) { try { widgets[widgetId] = this.decodeWidget(widgetId); } catch (error) { common_2.logger.error(`Error decoding widget ${widgetId}:`); common_2.logger.error(error); } } return widgets; } createWidget(widgetType) { let widget; if (widgetType === 0) { widget = new ContainerWidget(); } else if (widgetType === 1) { widget = new TextWidget(); } else if (widgetType === 2) { widget = new InteractableItemWidget(); } else if (widgetType === 3) { widget = new RectangleWidget(); } else if (widgetType === 4) { widget = new LinkWidget(); } else if (widgetType === 5) { widget = new SpriteWidget(); } else if (widgetType === 6) { widget = new ModelWidget(); } else if (widgetType === 7) { widget = new StaticItemWidget(); } else if (widgetType === 8) { widget = new TooltipWidget(); } else if (widgetType === 9) { widget = new LineWidget(); } return widget; } decodeWidgetFormat2(widgetId, inputBuffer) { const buffer = new common_1.ByteBuffer(inputBuffer); buffer.readerIndex = 1; // skip the first byte for the new format const widgetType = buffer.get('BYTE'); const widget = this.createWidget(widgetType); widget.id = widgetId; widget.format = 2; widget.contentType = buffer.get('SHORT', 'UNSIGNED'); widget.originalX = buffer.get('SHORT'); widget.originalY = buffer.get('SHORT'); widget.width = buffer.get('SHORT', 'UNSIGNED'); widget.x = widget.originalX; widget.y = widget.originalY; if (widget instanceof LineWidget) { widget.height = buffer.get('SHORT'); } else { widget.height = buffer.get('SHORT', 'UNSIGNED'); } widget.parentId = buffer.get('SHORT', 'UNSIGNED'); if (widget.parentId === 0xffff) { widget.parentId = -1; } widget.hidden = buffer.get('BYTE', 'UNSIGNED') === 1; widget.hasListeners = buffer.get('BYTE', 'UNSIGNED') === 1; if (widget instanceof ContainerWidget) { widget.scrollWidth = buffer.get('SHORT', 'UNSIGNED'); widget.scrollPosition = buffer.get('SHORT', 'UNSIGNED'); } if (widget instanceof SpriteWidget) { widget.spriteId = buffer.get('INT'); widget.textureId = buffer.get('SHORT', 'UNSIGNED'); widget.tiled = buffer.get('BYTE', 'UNSIGNED') === 1; widget.opacity = buffer.get('BYTE', 'UNSIGNED'); } if (widget instanceof ModelWidget) { widget.modelType = 1; widget.modelId = buffer.get('SHORT', 'UNSIGNED'); widget.offsetX = buffer.get('SHORT'); widget.offsetY = buffer.get('SHORT'); widget.rotationX = buffer.get('SHORT', 'UNSIGNED'); widget.rotationZ = buffer.get('SHORT', 'UNSIGNED'); widget.rotationY = buffer.get('SHORT', 'UNSIGNED'); widget.modelZoom = buffer.get('SHORT', 'UNSIGNED'); widget.animation = buffer.get('SHORT', 'UNSIGNED'); widget.orthogonal = buffer.get('BYTE', 'UNSIGNED') === 1; if (widget.animation === 65535) { widget.animation = -1; } if (widget.modelId === 65535) { widget.modelId = -1; } } if (widget instanceof LinkWidget) { widget.fontId = buffer.get('SHORT', 'UNSIGNED'); widget.text = buffer.getString(); widget.lineHeight = buffer.get('BYTE', 'UNSIGNED'); widget.textAlignmentX = buffer.get('BYTE', 'UNSIGNED'); widget.textAlignmentY = buffer.get('BYTE', 'UNSIGNED'); widget.textShadowed = buffer.get('BYTE', 'UNSIGNED') === 1; widget.textColor = buffer.get('INT'); } if (widget instanceof RectangleWidget) { widget.textColor = buffer.get('INT'); widget.filled = buffer.get('BYTE', 'UNSIGNED') === 1; widget.opacity = buffer.get('BYTE', 'UNSIGNED'); } if (widget instanceof LineWidget) { widget.lineWidth = buffer.get('BYTE', 'UNSIGNED'); widget.textColor = buffer.get('INT'); } if (widget.hasListeners) { // @TODO decode listeners } return widget; } decodeWidgetFormat1(widgetId, inputBuffer) { const buffer = new common_1.ByteBuffer(inputBuffer); const widgetType = buffer.get('BYTE'); const widget = this.createWidget(widgetType); widget.id = widgetId; widget.format = 1; widget.menuType = buffer.get('BYTE', 'UNSIGNED'); widget.contentType = buffer.get('SHORT', 'UNSIGNED'); widget.originalX = buffer.get('SHORT'); widget.originalY = buffer.get('SHORT'); widget.width = buffer.get('SHORT', 'UNSIGNED'); widget.height = buffer.get('SHORT', 'UNSIGNED'); widget.opacity = buffer.get('BYTE', 'UNSIGNED'); widget.parentId = buffer.get('SHORT', 'UNSIGNED'); widget.hoveredSiblingId = buffer.get('SHORT', 'UNSIGNED'); widget.x = widget.originalX; widget.y = widget.originalY; if (widget.parentId === 0xffff) { widget.parentId = -1; } if (widget.hoveredSiblingId === 0xffff) { // 0xffff === 65535 widget.hoveredSiblingId = -1; } const alternateCount = buffer.get('BYTE', 'UNSIGNED'); if (alternateCount > 0) { widget.alternateOperators = new Array(alternateCount); widget.alternateRhs = new Array(alternateCount); for (let i = 0; alternateCount > i; i++) { widget.alternateOperators[i] = buffer.get('BYTE', 'UNSIGNED'); widget.alternateRhs[i] = buffer.get('SHORT', 'UNSIGNED'); } } const clientScriptCount = buffer.get('BYTE', 'UNSIGNED'); if (clientScriptCount > 0) { widget.cs1 = new Array(clientScriptCount); for (let csIndex = 0; csIndex < clientScriptCount; csIndex++) { const k = buffer.get('SHORT', 'UNSIGNED'); widget.cs1[csIndex] = new Array(k); for (let j = 0; k > j; j++) { widget.cs1[csIndex][j] = buffer.get('SHORT', 'UNSIGNED'); if (widget.cs1[csIndex][j] === 65535) { widget.cs1[csIndex][j] = -1; } } } } if (widget instanceof ContainerWidget) { widget.scrollHeight = buffer.get('SHORT', 'UNSIGNED'); widget.hidden = buffer.get('BYTE', 'UNSIGNED') === 1; } if (widget instanceof TextWidget) { buffer.get('SHORT', 'UNSIGNED'); // @TODO look into these at some point buffer.get('BYTE', 'UNSIGNED'); } if (widget instanceof InteractableItemWidget) { widget.items = new Array(widget.height * widget.width); widget.itemAmounts = new Array(widget.height * widget.width); widget.itemSwapable = buffer.get('BYTE', 'UNSIGNED') === 1; widget.isInventory = buffer.get('BYTE', 'UNSIGNED') === 1; widget.itemUsable = buffer.get('BYTE', 'UNSIGNED') === 1; widget.itemDeletesDraged = buffer.get('BYTE', 'UNSIGNED') === 1; widget.itemSpritePadsX = buffer.get('BYTE', 'UNSIGNED'); widget.itemSpritePadsY = buffer.get('BYTE', 'UNSIGNED'); widget.imageX = new Array(20); widget.imageY = new Array(20); widget.images = new Array(20); for (let sprite = 0; sprite < 20; sprite++) { const hasSprite = buffer.get('BYTE', 'UNSIGNED'); if (hasSprite === 1) { widget.images[sprite] = buffer.get('SHORT'); widget.imageX[sprite] = buffer.get('SHORT'); widget.imageY[sprite] = buffer.get('INT'); } else { widget.imageY[sprite] = -1; } } widget.options = new Array(5); for (let i = 0; i < 5; i++) { widget.options[i] = buffer.getString(); if (widget.options[i].length === 0) { widget.options[i] = null; } } } if (widget instanceof RectangleWidget) { widget.filled = buffer.get('BYTE', 'UNSIGNED') === 1; } if (widget instanceof LinkWidget || widget instanceof TextWidget) { widget.textAlignmentX = buffer.get('BYTE', 'UNSIGNED'); widget.textAlignmentY = buffer.get('BYTE', 'UNSIGNED'); widget.lineHeight = buffer.get('BYTE', 'UNSIGNED'); widget.fontId = buffer.get('SHORT', 'UNSIGNED'); widget.textShadowed = buffer.get('BYTE', 'UNSIGNED') === 1; } if (widget instanceof LinkWidget) { widget.text = buffer.getString(); widget.alternateText = buffer.getString(); } if (widget instanceof TextWidget || widget instanceof RectangleWidget || widget instanceof LinkWidget) { widget.textColor = buffer.get('INT'); } if (widget instanceof RectangleWidget || widget instanceof LinkWidget) { widget.alternateTextColor = buffer.get('INT'); widget.hoveredTextColor = buffer.get('INT'); widget.alternateHoveredTextColor = buffer.get('INT'); } if (widget instanceof SpriteWidget) { widget.spriteId = buffer.get('INT'); widget.alternateSpriteId = buffer.get('INT'); } if (widget instanceof ModelWidget) { widget.modelType = 1; widget.alternateModelType = 1; widget.modelId = buffer.get('SHORT', 'UNSIGNED'); widget.alternateModelId = buffer.get('SHORT', 'UNSIGNED'); widget.animation = buffer.get('SHORT', 'UNSIGNED'); widget.alternateAnimation = buffer.get('SHORT', 'UNSIGNED'); widget.modelZoom = buffer.get('SHORT', 'UNSIGNED'); widget.rotationX = buffer.get('SHORT', 'UNSIGNED'); widget.rotationY = buffer.get('SHORT', 'UNSIGNED'); if (widget.modelId === 0xffff) { widget.modelId = -1; } if (widget.alternateModelId === 0xffff) { widget.alternateModelId = -1; } if (widget.animation === 0xffff) { widget.animation = -1; } if (widget.alternateAnimation === 0xffff) { widget.alternateAnimation = -1; } } if (widget instanceof StaticItemWidget) { widget.items = new Array(widget.width * widget.height); widget.itemAmounts = new Array(widget.width * widget.height); widget.textAlignmentX = buffer.get('BYTE', 'UNSIGNED'); widget.fontId = buffer.get('SHORT', 'UNSIGNED'); widget.textShadowed = buffer.get('BYTE', 'UNSIGNED') === 1; widget.textColor = buffer.get('INT'); widget.itemSpritePadsX = buffer.get('SHORT'); widget.itemSpritePadsY = buffer.get('SHORT'); widget.isInventory = buffer.get('BYTE', 'UNSIGNED') === 1; widget.options = new Array(5); for (let i = 0; i < 5; i++) { widget.options[i] = buffer.getString(); if (widget.options[i].length === 0) { widget.options[i] = null; } } } if (widget instanceof TooltipWidget) { widget.text = buffer.getString(); } if (widget.menuType === 2 || widget instanceof InteractableItemWidget) { widget.targetVerb = buffer.getString(); widget.spellName = buffer.getString(); widget.clickMask = buffer.get('SHORT', 'UNSIGNED'); } if (widget.menuType === 1 || widget.menuType === 4 || widget.menuType === 5 || widget.menuType === 6) { widget.hintText = buffer.getString(); if (widget.hintText.length === 0) { if (widget.menuType === 1) { widget.hintText = 'Ok'; } else if (widget.menuType === 4 || widget.menuType === 5) { widget.hintText = 'Select'; } else if (widget.menuType === 6) { widget.hintText = 'Continue'; } } } return widget; } } exports.WidgetStore = WidgetStore;