Library / SDK
AlexAltea/unicorn.js avatar
AlexAltea/unicorn.js

unicorn.js: a CPU emulator framework that runs in the browser

Unicorn CPU emulator framework port for JavaScript

603 stars45 forksJavaScriptGPL-2.0

At a glance

What is it?
Unicorn.js is a port of the Unicorn CPU emulator engine to JavaScript and WebAssembly, built with Emscripten. It gives a web application the ability to execute machine code it does not natively support, which is what makes it useful for reverse engineering tooling, obfuscation analysis, and anything else that needs to inspect code as data. The build script also produces per-architecture bundles so a page pays only for the instruction set it uses.
Who is it for?
unicorn.js fits a developer building browser based reverse engineering tooling, a teaching tool that shows what machine code does, or anything that needs to run an unfamiliar instruction set in a sandboxed way inside a page. For a project that only needs one architecture, the per-architecture bundle keeps the download manageable, and it does not fit a server side job where the native library is already available.
Can I use it commercially?
Yes, with conditions. GPL-2.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 120 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The engine, and why it is in a browser

Unicorn.js is a port of the Unicorn CPU emulator framework for JavaScript and WebAssembly, powered by Emscripten. The upstream project is described in the readme as a lightweight multi-architecture CPU emulator framework originally developed by two named authors and released under a version 2 of the GNU General Public Licence, with the contributors and the licence terms in a set of files inside the unicorn submodule of this repository.

The technical decision that makes the port possible is Emscripten, which compiles a C codebase to WebAssembly. Unicorn is written in C, and a C library that emulates CPU instruction sets is exactly the kind of thing Emscripten is good at translating, because the job is arithmetic and memory access rather than system calls. The result is an emulator that runs inside a browser tab, which is not a small thing: CPU emulation normally means a native library, and a native library normally means a server.

And that is the point of the port. A browser application that can execute machine code it does not support has a different set of possibilities from one that cannot. Reverse engineering in a browser is the obvious case, and the package's own keyword list includes it. A tool that disassembles a sample, or that runs a decryption routine, or that checks what a piece of obfuscated code computes, normally needs a native process and a server behind it. With the emulator in the page, that work can happen locally, on data that never leaves the machine.

The readme is careful about the requirements, and they are worth reading exactly. You need a JavaScript environment with WebAssembly support and with BigInt support. The BigInt requirement is not incidental: a CPU emulator has to model 64-bit registers and 64-bit arithmetic, and in JavaScript the only way to represent a 64-bit value without losing precision is the big integer type.

The licence situation has two layers, and it is worth separating them. This port is under the version 2 of the GNU General Public Licence, and the upstream engine it wraps is also under that licence, with the submodule carrying the upstream's own author and contributor files. So the whole stack, port and engine together, is copyleft, and the obligations of that licence apply to the combined work rather than to the wrapper alone.

Installing it, and the per-architecture bundles

There are two ways to get the library, and they suit two different situations.

The first is to download the built file and include it with a script tag.

html
<script src="unicorn.js"></script>

That is the no-build route, and for a single page experiment it is the fastest way to see whether the thing works.

The second is the package manager, and the package name is scoped.

bash
npm install @alexaltea/unicorn-js

Then there is the third option, which is the most interesting one and the reason the readme has a section of its own for it.

The default build supports all architectures, and if you only need one, smaller per-architecture variants are published alongside it and can be selected through a subpath on the require or the import call.

javascript
const MUnicorn = require('@alexaltea/unicorn-js');      // all architectures
const MUnicorn = require('@alexaltea/unicorn-js/x86');  // x86 only

The comment on the first line is the point: the default is the fat one, and the second line is the one you want if you know what you are emulating.

The full list of variants is ten architectures, and the list is a decent summary of what a general purpose emulator has to cover: ARM, AArch64, the Motorola 68k, MIPS, PowerPC, RISC-V, the IBM s390x, SPARC, a Tricore variant, and x86. That is a broad spread of instruction sets, and each of them is a separate emulator inside the upstream engine, so a build that carries all of them carries a great deal of code.

The package manifest shows the mechanism precisely. The main entry point is the all-architecture bundle, and the exports map lists one subpath per architecture, each pointing at its own file. The published files array contains only the distribution directory, so the sources and the tests are not shipped.

And the variants are also available directly from a content delivery network, with the readme giving the pattern for the x86 one. So a page that wants a single architecture can pull a single file from a CDN without a build step, which is the same trick as the script tag but scoped down.

The reason to care about the size is simple arithmetic. The engine is C code compiled to WebAssembly, and WebAssembly modules are not small, so a page that loads every instruction set to emulate one of them is paying for a lot of code it will never execute.

The API, and what an emulation session looks like

The usage example is the whole API in thirty lines, and it is worth reading closely because the shape of it is the shape of every emulator API.

The library is initialised asynchronously, and the promise resolves to an object that carries the architecture and mode constants. That is an unusual design detail worth pausing on: instead of exporting the constants at module level, they come from the instantiated module. It means the constants belong to a particular build, and it means you cannot use a constant before the module is ready.

javascript
var addr = 0x10000;
var code = [
  0x37, 0x00, 0xA0, 0xE3,  // mov r0, #0x37
  0x03, 0x10, 0x42, 0xE0,  // sub r1, r2, r3
];

The code is a plain array of bytes with the instructions commented alongside, which is how you write machine code in a language with no assembler. The two instructions are a move of a constant into a register and a subtraction, and the comments say so.

Then the engine object is created with an architecture and a mode, and here the constants come from the resolved module rather than from a global namespace.

javascript
var e = new uc.Unicorn(uc.ARCH_ARM, uc.MODE_ARM);

The next block is the setup, and it is the part that matters for safety. Registers are written by name, taken from the same constants, and then memory is mapped with a size and a protection mask before the code is written into it.

javascript
e.reg_write_i32(uc.ARM_REG_R2, 0x456);
e.reg_write_i32(uc.ARM_REG_R3, 0x123);
e.mem_map(addr, 4*1024, uc.PROT_ALL);
e.mem_write(addr, code);

Mapping memory explicitly, rather than letting the emulator allocate on demand, is the equivalent of drawing a fence around the program. An emulator that can execute arbitrary instructions is a program that will happily read and write outside its intended area if you let it, and the map call is where you say where it may go. The protection mask is the other half, since read, write, and execute permissions can be given separately.

Then the run. The call takes the begin address, the until address, a timeout, and a count, and the example sets the until address to the end of the code, so the emulation stops when it reaches it.

javascript
var begin = addr;
var until = addr + code.length;
e.emu_start(begin, until, 0, 0);

The result is read back from the registers, and the comments give the expected values, which makes the example self-checking: the move should have put the constant in the first register, and the subtraction should have produced the difference of the two values written earlier.

Finally the engine is closed, which releases the emulated memory. In a page that emulates repeatedly, that call is what keeps memory from growing without bound, and the presence of it at the end of the example is a hint about the intended usage pattern.

So the sequence is: initialise, create, set registers, map memory, write code, run, read results, close. Every step is explicit, and there is no automatic allocation, no implicit state, and no way to run code that has not been given a mapped region to run in.

Building it from source

The building section is three steps, and the middle one is where the difficulty is.

The first step is to clone the repository including its submodules, and the word including is the important part.

bash
git clone --recursive https://github.com/AlexAltea/unicorn.js

The submodule is the upstream Unicorn engine itself, and the repository listing at the top level shows a directory with that name, alongside a gitmodules file. A build that does not have the upstream source cannot compile, and the recursive flag is what fetches it.

The second step is to install a recent Python 3, CMake, and the Emscripten SDK, and to follow the corresponding instructions and make sure the environment variables are configured correctly. Three tools, two of which are build systems, and the Emscripten one is the compiler that does the actual translation from C to WebAssembly.

The third step is to run the build script.

bash
python3 build.py

The artefacts land in the distribution directory, and the package manifest confirms that this is where the published files come from, since the files array contains only that directory.

Then there is a tip at the end which is the practical instruction for most users, and it points at the same per-architecture idea as the package exports. Pass architecture names to produce a smaller single-architecture bundle, for example by giving one name to the script, or pass the release flag to build every variant at once.

So the build script takes the architecture list as its argument, and the same script produces either one bundle or the full set. That is a small interface, and it means the per-architecture files that the package exports point at are not a separate build system but the same build with different arguments.

The package manifest's scripts tie the two together. The build script is the same Python script the documentation tells you to run, the release script adds the flag, and a pre-publish hook runs the release build, so a published package is always a full set of variants rather than whatever happened to be in the working tree. The test script runs a Node file from the tests directory, and there is a serve script for the documentation page, with an index file at the top level of the repository.

The upstream project, and the licence boundary

There is a paragraph in the readme about the upstream engine that is easy to skip and that determines what you can do with the result.

Unicorn is described as a lightweight multi-architecture CPU emulator framework, originally developed by Nguyen Anh Quynh, Dang Hoang Vu and others, and released under the version 2 of the GNU General Public Licence. More information about the contributors and the licence terms can be found in the author, credit, and copying files inside the unicorn submodule of the repository.

The instruction to look inside the submodule is the interesting part, because it tells you that the licence position is not settled by the port's own licence file alone. The port is under the same licence, but the terms that apply to the combined work are the upstream's terms, and the upstream keeps its own author list, its own credit file, and its own copy of the licence text. A GPL project that vendors another GPL project has to keep the second one's notices intact, and the submodule plus those three files are how that obligation is met.

So the practical guidance for someone considering this is simple. The whole stack, port and engine, is copyleft under the version 2 of the GNU General Public Licence, and that applies to what you distribute. Linking it into a page that runs in a browser and is served to users is distribution, and the obligations attach. A project that cannot meet them needs a different approach, and this is a fact worth establishing before the integration work rather than after.

The upstream's own design is also worth understanding, because it is what makes the port work. The readme's description of Unicorn as lightweight and multi-architecture is accurate and relevant here: the engine is a set of per-architecture backends behind a common interface, and the interface is what the Emscripten port exposes. The ten architecture variants in the package exports are ten instantiations of that common interface, and the shared code around them is what the all-architecture bundle contains.

The other credit in the readme is to the person who did the port and the build work, and the manifest names the author, with the version, the description, and the keyword list that includes reverse engineering. The project is published under a scoped package name, with a homepage pointing at a documentation site, and the last push was on 2026-06-25 with the most recent release, 2.1.4, in June 2026.

Editorial conclusion

unicorn.js fits a developer building browser based reverse engineering tooling, a teaching tool that shows what machine code does, or anything that needs to run an unfamiliar instruction set in a sandboxed way inside a page. For a project that only needs one architecture, the per-architecture bundle keeps the download manageable, and it does not fit a server side job where the native library is already available. Verify first which architecture you actually need and take the per-architecture bundle, confirm the page has the BigInt support the engine's 64-bit handling needs, and confine the emulated code to memory you mapped yourself, since an emulator that can execute arbitrary instructions is only as safe as the bounds you gave it.

Frequently asked questions

What is the unicorn.js project?

It is a port of the Unicorn CPU emulator framework to JavaScript and WebAssembly, built with Emscripten. It lets a web application execute machine code for architectures the browser does not support, which is what makes browser based reverse engineering and code analysis tooling possible without a native server.

What do I need to run unicorn.js?

A JavaScript environment with WebAssembly support and with BigInt support. The BigInt requirement is the one that surprises people, and it is there because the engine has to model 64-bit registers and arithmetic, which in JavaScript needs the big integer type to avoid losing precision.

How do I install unicorn.js?

Either include the built file with a script tag, or install the scoped package from npm. There is also a per-architecture option, because the default build supports all architectures and smaller variants are published alongside it and selected through a subpath on the require or import call, or pulled directly from a content delivery network.

Which architectures does unicorn.js support?

Ten variants are published: ARM, AArch64, the Motorola 68k, MIPS, PowerPC, RISC-V, s390x, SPARC, a Tricore variant, and x86. The build script takes the architecture names as arguments, so the per-architecture bundles are the same build with a narrower list, and the release flag builds every variant.

What licence applies to unicorn.js and the engine it wraps?

The version 2 of the GNU General Public Licence, for the port and for the upstream engine alike. The upstream keeps its own author file, credit file, and licence text inside the submodule of the repository, and those terms are what apply to the combined work, so the copyleft obligations attach to anything you distribute that includes it.

Official sources

  1. AlexAltea/unicorn.js on GitHub
  2. License: GPL-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/alexaltea-unicorn-js.svg)](https://hysenlabs.com/projects/alexaltea-unicorn-js)

Was this useful?