@runejs/filestore
Version:
Tools for managing the RuneJS filestore.
542 lines (541 loc) • 19.4 kB
JavaScript
;
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;