Code Monkey home page Code Monkey logo

homm3-unpacker's Introduction

homm3 unpacker

Parse file formats used by Heroes of Might and Magic III in node.js or modern browsers.


If there is any interest in the modding community (to say, edit files in browser), I can easily make DEFs/LODs repackable.

If anyone is in need of parsing some other file type (H3M, H3C, SND, VID…) feel free to open an issue and I'll see what I can do.

Usage

Node.js

npm install homm3-unpacker
const { unpackLOD, unpackDEF, unpackPCX } = require("homm3-unpacker")

Browser

<script src="dist/homm3-unpacker.js"></script>
const { unpackLOD, unpackDEF, unpackPCX } = window["homm3-unpacker"]

API

unpackLOD (buffer, processor)

Parse .LOD archive files.

buffer » binary data of a file - ArrayBuffer/node.js Buffer.

processor(buffer, filename, skip) » function to process extracted binary data for each contained file or object with processors for specific file formats; calling skip discards the result - function/object - defaults to (buffer) => buffer.

unpackLOD(file)
// {
//    type: "lod (expansion)",
//    files: {
//       "ab.h3c": ArrayBuffer,
//       "AdvEvent.txt": ArrayBuffer,
//       "Ar_Bg.pcx": ArrayBuffer,
//       …
//    }
// }
unpackLOD(file, (buffer, filename, skip) => (filename === "BoArt084.pcx" ? buffer : skip()))
// {
//    type: "lod (expansion)",
//    files: {
//       "BoArt084.pcx": ArrayBuffer
//    }
// }
unpackLOD(file, {
   "h3c": (buffer, filename, skip) => skip(),
   "txt": (buffer) => buffer,
   "pcx": (buffer) => buffer
})
// {
//    type: "lod (expansion)",
//    files: {
//       "AdvEvent.txt": ArrayBuffer,
//       "Ar_Bg.pcx": ArrayBuffer,
//       …
//    }
// }

unpackDEF (buffer, options)

Parse .DEF animation files.

buffer » binary data of a file - ArrayBuffer/node.js Buffer.

options.format » format to encode image data to - "png"/"bitmap" - defaults to "png".

options.padding » include padding around images - boolean - defaults to false.

options.palette(colours) » function to process the file's colour palette - function - defaults to (colours) => colours.

unpackDEF(file, { format: "png", padding: true })
// {
//    type: "def (creature)",
//    fullWidth: 450,
//    fullHeight: 400,
//    palette: [                             // 256 rgba colours.
//       { r: 0, g: 0, b: 0, a: 0 },
//       { r: 0, g: 0, b: 0, a: 64 },
//       { r: 255, g: 100, b: 255, a: 0 },
//       …
//    ],
//    groups: [
//       "moving": ["CAbehe10.pcx", "CAbehe11.pcx", "CAbehe12.pcx", "CAbehe13.pcx", "CAbehe14.pcx", …],
//       "mouse over": ["CAbehe01.pcx", "CAbehe05.pcx", "CAbehe06.pcx", "CAbehe07.pcx", "CAbehe08.pcx", …]
//       …
//    ],
//    images: {
//       "CAbehe10.pcx": {
//          width: 116,
//          height: 110,
//          x: 173,
//          y: 158,
//          data: ArrayBuffer                // PNG file.
//       },
//       "CAbehe01.pcx": {
//          width: 88,
//          height: 106,
//          x: 174,
//          y: 165,
//          data: ArrayBuffer,
//          selection: ArrayBuffer           // Selection extracted into separate PNG.
//       },
//       …
//    }
// }

unpackPCX (buffer, options)

Parse .PCX images.

buffer » binary data of a file - ArrayBuffer/node.js Buffer.

options.format » format to encode image data to - "png"/"bitmap" - defaults to "png".

options.transparency » array of colours to make transparent when they are the first colour in palette - defaults to [{r:0,g:255,b:255}, {r:255,g:0,b:255}, {r:0,g:0,b:0}].

unpackPCX(file, { format: "png" })
// {
//    type: "pcx (indexed)",
//    width: 58,
//    height: 64,
//    data: ArrayBuffer                      // PNG file.
// }

Note: not to be confused with PCX images in DEFs.

Examples

Reading/writing to files with the help of these two.

const fs = require("graceful-fs")        // So we don't care about the number of open files.

function read(path) {
   return new Promise((resolve, reject) => {
      fs.readFile(path, (error, data) => (error ? reject(error) : resolve(data)))
   })
}

function write(path, buffer) {
   return new Promise((resolve, reject) => {
      fs.writeFile(path, Buffer.from(buffer), (error) => (error ? reject(error) : resolve()))
   })
}

Extract all files from a LOD and keep them in memory.

Without a second parameter, the extracted files are stored as binary data in the .files dictionary object, under their respective filenames.

async function example1(path) {
   const file = await read(path)
   return unpackLOD(file)
}

Previous example is equivalent to the following when a processor function is supplied as the second parameter.

The callback is invoked with (buffer, filename) for every file and its return value stored in .files dictionary.

async function example2(path) {
   const file = await read(path)
   return unpackLOD(file, (buffer) => buffer)
}

When passing an object as the second parameter, its keys are matched against the file extension (case insensitively), and the callback's return value is stored instead of raw binary data.

To parse all DEF files with unpackDEF and store any other file as ArrayBuffer:

async function example3(path) {
   const file = await read(path)
   return unpackLOD(file, {
      def: (buffer) => unpackDEF(buffer)
   })
}

// Equivalent to this.
async function example4(path) {
   const file = await read(path)
   return unpackLOD(file, (buffer, filename) => (/\.def$/i.test(filename) ? unpackDEF(buffer) : buffer))
}

Unpacking images from large LODs can take several minutes, it'd be nice to get some feedback while running. Let's log the completion of each parsed DEF:

async function example5(path) {
   const file = await read(path)
   return unpackLOD(file, {
      def: (buffer, filename) => {
         const result = unpackDEF(buffer)
         console.log(`Unpacked "${filename}".`)
         return result
      }
   })
}

To extract files archived in a LOD into separate files.

// Extract while parsing.
async function example6(path) {
   const file = await read(path)
   unpackLOD(file, (buffer, filename) => { write(filename, buffer) })
}

// Extract after parsing.
async function example7(path) {
   const file = await read(path)
   const lod = unpackLOD(file)
   for (const [filename, buffer] of Object.entries(lod.files))
      write(filename, buffer)
}

The following would extract every image in a LOD (animation frames and standalone images) as a PNG file.

async function example8(path) {
   const file = await read(path)
   const lod = unpackLOD(file, {
      def: (buffer, filename) {
         const def = unpackDEF(buffer, { format: "png", padding: true })
         for (const [filename, image] of Object.entries(def.images)) {
            write(filename, image.data)
            if (image.selection)
               write(filename, image.selection)
         }
      },
      pcx: (buffer, filename) {
         const image = unpackPCX(buffer, { format: "png" })
         write(filename, image.data)
      }
   })
}

homm3-unpacker's People

Contributors

christopherrotter avatar neunato avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

homm3-unpacker's Issues

New file formats support

I wonder if there will be an option to pull and add new files over time. In general, I want (in theory) to do a LibreVCMI project that would allow the creation of a "free version of Heroes 3".

In general, I see 2 possibilities here:

  • Replacing graphics, effects, etc. with new ones.
  • Somehow making everything from scratch.

I don't know if it's realistically possible to make everything from scratch and implement it into VCMI / FreeHeroes, or if it's better to modify the files. On the other hand, I don't think there are many tools (open source) that allow you to rip and swap game files.

And I wonder if you will want to develop your code with additional file support.

Artifacts in unpacked PCX images

Some data returned by unpackPCX shows artifacts - transparency where there should be none. For example, this is a section of the unpacked file CmBkSub.pcx :

image

I haven't looked into the implementation details, perhaps there is an easy fix to it?

Web Version | PNGs Downloader

Hi, as I see you have delpoyed web version on GitHub Pages.
It'd be nice to introduce here download option, which will download all frames in PNGs from the def file :)

Or I can do it if you are so kind to share the github pages source code :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.