@lbalab/hqr

A javascript HQR reader/writer for node.js and browser

Downloads in past

Stats

StarsIssuesVersionUpdatedCreatedSize
@lbalab/hqr
403.4.418 days ago2 years agoMinified + gzip package size for @lbalab/hqr in KB

Readme

HQR Library
!npm packagenpm-imgnpm-url !Build Statusbuild-imgbuild-url !Downloadsdownloads-imgdownloads-url !Issuesissues-imgissues-url !Code Coveragecodecov-imgcodecov-url !Commitizen Friendlycommitizen-imgcommitizen-url !Semantic Releasesemantic-release-imgsemantic-release-url
A javascript HQR reader/writer for node.js and web browsers. HQR is a simple file format used by the Little Big Adventure 1 & 2 games for storing data.
A HQR file consists of a header followed by a series of entries which are either compressed or not. It can also contain hidden entries, as a mechanism to store several entries in the same index.
For more information about the content of the various HQR files found in LBA1 and LBA2 game folders, see herelba-file-info-url.

Install

npm install @lbalab/hqr

Examples

Make sure to backup your game files before using this library to mess with them.

Read the palette data:

const fs = require('fs/promises');
const { HQR } = require('@lbalab/hqr');

(async () => {
  const file = await fs.readFile('LBA2/RESS.HQR');
  const hqr = HQR.fromArrayBuffer(
    file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength)
  );
  console.log(hqr.entries[0].content); // main game palette as an ArrayBuffer
})();

Swap HQR entries:

const fs = require('fs/promises');
const { HQR } = require('@lbalab/hqr');

/*
 ** Swap regular trees with palm trees in citadel island (with rain):
 ** Entry #0: regular tree
 ** Entry #1: palm tree
 */
(async () => {
  const file = await fs.readFile('LBA2/CITADEL.OBL');
  const hqr = HQR.fromArrayBuffer(
    file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength)
  );
  const tmp = hqr.entries[0];
  hqr.entries[0] = hqr.entries[4];
  hqr.entries[4] = tmp;
  await fs.writeFile('LBA2/CITADEL.OBL', Buffer.from(hqr.toArrayBuffer()));
})();

Change an HQR entry's content:

const fs = require('fs/promises');
const { HQR } = require('@lbalab/hqr');

(async () => {
  const file = await fs.readFile('LBA2/TEXT.HQR');
  const hqr = HQR.fromArrayBuffer(
    file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength)
  );
  const textBank = new Uint8Array(hqr.entries[1].content);
  /* The string "Resume Game" is at offset 943.
   ** Let's replace the letters e => é.
   */
  textBank[944] = 130;
  textBank[948] = 130;
  textBank[953] = 130;
  fs.writeFile('LBA2/TEXT.HQR', Buffer.from(hqr.toArrayBuffer()));
})();

Create an HQR file programmatically:

const fs = require('fs/promises');
const { HQR, HQREntry, CompressionType } = require('@lbalab/hqr');

/*
 ** This creates a SIMPLE.HQR file from scratch that contains 3 uncompressed entries:
 ** #0: 512 bytes entry filled with 0s
 ** #1: utf-8 string: "Hello world!"
 ** #2: 32 bit unsigned integer with value 42 (little endian)
 */
(async () => {
  const hqr = new HQR();

  // Entry #0: 512 bytes filled with 0s
  hqr.entries.push(new HQREntry(new ArrayBuffer(512), CompressionType.NONE));

  // Entry #1: utf-8 string: "Hello world!"
  const str = 'Hello world!';
  hqr.entries.push(
    new HQREntry(Buffer.from(str, 'utf-8'), CompressionType.NONE)
  );

  // Entry #2: 32 bit unsigned integer with value 42 (little endian)
  const numView = new DataView(new ArrayBuffer(4));
  numView.setUint32(0, 42, true);
  hqr.entries.push(new HQREntry(numView.buffer, CompressionType.NONE));

  await fs.writeFile('SIMPLE.HQR', Buffer.from(hqr.toArrayBuffer()));
})();

Show hidden entries information:

const fs = require('fs/promises');
const { HQR } = require('@lbalab/hqr');

(async () => {
  if (process.argv.length < 3) {
    console.error('Usage: node count-hidden.js <HQR file>');
    process.exit(1);
  }
  const file = await fs.readFile(process.argv[2]);
  const hqr = HQR.fromArrayBuffer(
    file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength)
  );
  for (let i = 0; i < hqr.entries.length; i++) {
    const entry = hqr.entries[i];
    if (!entry) continue;

    console.log(`Entry ${i} has ${entry.hiddenEntries.length} hidden entries:`);
    let j = 0;
    for (const hEntry of entry.hiddenEntries) {
      console.log(`  Hidden entry #${j}: ${hEntry.content.byteLength} bytes`);
      j++;
    }
  }
})();

/* Example output:

$> node count-hidden.js LBA2/VOX/EN_000.VOX
Entry 154 has 4 hidden entries:
  Hidden entry #0: 187692 bytes
  Hidden entry #1: 156532 bytes
  Hidden entry #2: 144304 bytes
  Hidden entry #3: 78528 bytes
Entry 205 has 1 hidden entries:
  Hidden entry #0: 139520 bytes
Entry 232 has 1 hidden entries:
  Hidden entry #0: 184860 bytes
Entry 316 has 1 hidden entries:
  Hidden entry #0: 116344 bytes

**/

Contributing

Commit format should follow Angular's commit guidelinesangular-commit-url.