A 64-bit game launcher for Crysis 2.
Crysis 2 shipped with a 32-bit game executable only. The Crysis 2 Mod SDK, however, ships a complete set of 64-bit engine DLLs - they were built for the Sandbox editor, and no 64-bit game client was ever released. This launcher boots those x64 DLLs as a playable game client.
The game is completable start to finish on this launcher (verified by an external tester). A fourteen-level campaign run has since been made in a single process with the allocator's memory steered above the 4 GB line, with every cutscene playing.
Status: working, but rough around the edges. See Known issues.
Three things, and two of them are downloads.
| Where | |
|---|---|
| Crysis 2, patched to 1.9 | Maximum Edition and the Steam release are already 1.9. A disc copy normally needs the patch. |
| Crysis 2 Mod SDK (free, by Crytek) | archive.org - 1.4 GB, the file carries Crytek's own signature. Also on ModDB. |
launcher64.exe |
The Releases page here. One file, about 50 KB. |
The Mod SDK is not optional and it is not a nice-to-have: the 64-bit engine lives inside it.
Retail Crysis 2 ships a 32-bit game only, so without the SDK there is nothing on your disk for
this launcher to start. The SDK installs those 64-bit files into the game's Bin64 folder.
- Install Crysis 2 and make sure it is patched to 1.9.
- Install the Mod SDK into the same folder as the game.
- Copy
launcher64.exeintoBin64, next toCrySystem.dll. - Run
launcher64.exe.
When it is in the right place, the folder looks like this:
Crysis 2\
Bin32\Crysis2.exe the original 32-bit game, untouched
Bin64\CrySystem.dll came with the Mod SDK
Bin64\launcher64.exe what you downloaded
Nothing is overwritten and no game file is modified - the original 32-bit Crysis2.exe keeps
working exactly as before. To uninstall, delete launcher64.exe.
The launcher checks the common mistakes itself and says what is wrong in plain words, including the folder it looked in. If you get one of these boxes:
| What it says | What to do |
|---|---|
| The 64-bit engine (CrySystem.dll) was not found | launcher64.exe is not in Bin64, or the Mod SDK was never installed. Check the folder layout above. |
| CrySystem.dll does not export CreateSystemInterface | The engine files are from a different game or SDK. Reinstall the Crysis 2 Mod SDK. |
| The engine failed to start up | Send Game.log and launcher_diag.txt (both in the Crysis 2 folder) with a bug report. |
| "the side-by-side configuration is incorrect" | Windows cannot find the 2008 C runtime. Install the Visual C++ 2008 x64 Redistributable. This is rare: the launcher asks for Microsoft.VC90.CRT 9.0.21022.8 (amd64), the same assembly the SDK's own Editor.exe and CrySystem.dll ask for, so a working Mod SDK install normally already has it. |
Every launch writes launcher_diag.txt into the game folder, listing your hardware, display mode,
and the versions of every engine module and pak. Attach that file to a bug report; it usually
identifies the problem immediately.
Two flags exist if a fix causes trouble on your setup:
| Flag | Effect |
|---|---|
-noborderless |
leave the window alone, use whatever mode the game picks |
-keepintro |
keep the intro videos and the debug overlay |
-allowmultiple |
start a second copy without asking |
There are also diagnostic flags. They are not needed for playing, but they turn "it crashes on your machine and not on mine" into something answerable:
| Flag | Effect |
|---|---|
-traceallocs |
log every large engine allocation, its size and the address it got, to launcher_faults.txt |
-forcehighheap |
reserve all free memory below the 4 GB line before engine init, forcing the heap above it |
-noenginefix |
leave the pointer-width corrections out (they are applied by default) |
-enginefix:<mask> |
apply only selected correction sites, one bit each, for comparison |
-nocbfix |
leave the renderer's constant-buffer cache at its original size |
-noaifix |
let the AI call into a destroyed target, as the game does |
-nosndfix |
let the sound engine call into a destroyed object, as the game does |
-nomodfix |
leave the other modules' copies of the allocator truncating, as the game ships them |
-topdown[:KB] |
serve large reservations from above the 4 GB line, so truncation faults on demand |
-topdown:max |
put them at the very top of the address space instead (breaks dsound) |
-arenahigh |
serve the allocator's arenas from above the 4 GB line, to exercise the corrections |
-cbwatch |
arm a hardware watchpoint on the constant-buffer cache (suspends every thread once) |
-forcehighheap exists because pointer truncation only shows up when memory lands high, which on
most machines it never does. The flag makes that condition happen on demand, so a fix can be
tested in twenty seconds instead of by shipping a build and waiting for reports.
The x64 build has a hard framerate ceiling at almost exactly 64 FPS. It is not a GPU or CPU limit: with the lock active the GPU sits at ~5.6 ms and physics at ~0.1 ms, while the main thread spends ~15.5 ms per frame waiting on the physics barrier. The hardware is idle.
The cause is not the engine. It is the Windows timer quantum:
default timer resolution on Windows = 15.625 ms
1000 ms / 15.625 ms = 64.0 FPS <- the observed ceiling
measured WaitPhys = ~15.5 ms <- exactly one quantum
When the main thread waits on the physics barrier, Windows rounds that wait up to the next
scheduler quantum. One frame becomes one quantum. Checking the import tables confirms nobody
ever raises the resolution - Crysis2.exe, Editor.exe and CrySystem.dll all import only
timeGetTime from WINMM, never timeBeginPeriod:
Crysis2.exe (retail 32-bit) -> WINMM.dll: [timeGetTime]
Editor.exe -> WINMM.dll: [timeGetTime]
CrySystem.dll (x64) -> WINMM.dll: [timeGetTime]
This also explains the odd bimodal behaviour players reported - the framerate is either pinned at 64 or spikes to 200-300, with nothing in between. Frames that don't hit the wait run at full speed; frames that do hit it cost a whole quantum. That is a signature of a timer quantum, not of load.
The fix is timeBeginPeriod(1) in the launcher, before engine init. Since Windows 10 2004 the
call is per-process, so it affects only the game.
Result on an RTX 3060 @ 1920x1080: a hard 64 FPS becomes 270-330 FPS.
Note: this is listed as an open item in c2-launcher's TODO ("fix DX11 fps cap limit on fullscreen"), where the working hypothesis was to force borderless windowed. The real cause turned out to be unrelated to the window mode - the lock is present in windowed mode too.
With the framerate unlocked, exclusive fullscreen tears: the game renders well above the display
refresh rate. Enabling VSync is not a good answer here, because the engine has no refresh-rate
CVar at all - CryRenderD3D11 exposes only r_Fullscreen and r_VSync, so DXGI hands it 60 Hz
and VSync pins the game to 60 FPS with noticeable input lag, even on a 165 Hz display.
Windowed mode behaves completely differently: frames go through the Windows compositor, which synchronises presentation itself. Tearing is impossible by design, the framerate stays unlocked, and there is no VSync input lag.
So the launcher runs the game in a borderless window sized to the screen: it asks the engine to start windowed at desktop resolution, then a background thread finds the game window and strips its frame. The result looks like fullscreen and feels like windowed.
This also fixes Alt-Tab crashes for free - a windowed swapchain has no exclusive device to lose on a focus switch. Confirmed in practice: Alt-Tab is now fast and returns straight to a borderless fullscreen view.
The thread polls twice a second (never in the frame loop), keeps watching rather than applying
once (the engine can recreate its window on a video mode change), and leaves minimised windows
alone so it doesn't fight Alt-Tab. If the game ends up in exclusive fullscreen anyway, its window
is already borderless and full-screen, so the check finds nothing to do. Disable with
-noborderless.
Retail CrySystem.dll keeps the head of its bucket allocator's free-page list in a global that
every read treats as 64 bits wide and exactly one write treats as 32:
RVA 0x0A16F1 read 64-bit
RVA 0x0A1715 read 64-bit
RVA 0x0A1A30 read 64-bit
RVA 0x0A1A49 WRITE 32-bit mov dword ptr [rip+0x6578E9], ebp
The top half of the pointer is dropped on the way in and reads back as zero. In the original 32-bit game this was invisible, because addresses were 32 bits anyway. In a 64-bit process it stays invisible only while the allocation sits below the 4 GB line.
The launcher keeps it that way: it is built with the VC90 compiler from WDK 7.1 and links
msvcr90 as the primary CRT (/MD), so the process heap lands low - the same situation the
editor is in, which is why the editor never hit this. That is why build.ps1 uses the WDK
toolchain rather than a modern MSVC; it is load-bearing, not nostalgia.
A patch for the instruction itself exists (-enginefix): the store is one REX.W prefix short of
being correct, and the dead register reload in front of it frees exactly the byte that prefix
needs, so the fix is byte-for-byte the same length and no displacement moves. It is off by
default, because it turns out not to be needed - startup survives -forcehighheap, which forces
every allocation above the line, with the patch and without it. The engine is left alone unless
there is a reason to touch it.
- CryAction release asserts on the
CLevelSystem::LoadLevelpath force-crash in the Bin64 build. The launcher patches them out in memory at startup (addresses from c2-launcher). - CMovieSystem use-after-free: unloading a layer during a cutscene precache leaves dangling descriptors in the movie update list, which crash on Battery Park. The launcher installs a vectored exception handler that skips the corrupt entry instead of dying - worst case a broken node loses its animation.
With the allocator's arenas steered above the 4 GB line, a level's opening cutscene would start
and then stand still. The sequence was in the playing list, its length was read correctly, its
speed was 1.0 - and its clock never advanced by a single frame. The scene never reported Done,
so the level script waited forever and the player was left standing without a body. No crash, no
log line, nothing to search for.
The cause was this launcher's own workaround, not the engine. The guard that protects the movie
update loop from corrupt entries (section 4 above) rejected every pointer whose high half was
non-zero - a check that was correct when all of this game's memory really did sit below 4 GB.
Once the arenas move up, a sequence pointer of 0x2_1CF88110 is perfectly valid, and the guard
threw every one of them away, skipping the line that advances the clock.
Both guards were rewritten: a pointer is now checked against the real user-mode range, and the module bounds are read from the module's own PE header instead of hard-coded constants.
Measured as the distance the camera travels during the opening cutscene:
| Battery Park | FDR | |
|---|---|---|
| before | 0.004 | - |
| after | 4.816 / 4.766 | 5.875 |
A full campaign run followed: fourteen levels in a single process with memory steered high, 5.8 GB allocated above the 4 GB line, every cutscene playing.
This also retired a workaround. A band of allocations between 14 and 16 MB used to be pinned below the line, because with it high the game broke. It was never a cure - it simply kept the sequence pointers where that broken guard would accept them. The band now goes high with everything else.
Across a long campaign the renderer dies while copying a small struct of reference-counted pointers: one of them names an object that was released when a level unloaded, and its memory has since been handed out again. Seen twice, both times on a transition between levels, both times at the same instruction - once with two numbers sitting where a method table should be.
The repair waits for the fault rather than guarding every copy. A guard has to decide, on every single copy, whether a pointer is alive - and a guard that decides wrongly is exactly what froze the cutscenes above. Instead, when the fault happens, the destination field is cleared and execution resumes at the next line, leaving the pointer empty rather than dead; the code right below tests it against null three times over.
Pass -norndfix to disable it.
Two things made the build look like a debug build rather than a game.
The x64 CrySystem.dll is an editor build and enables r_DisplayInfo, which draws a debug
overlay whose status line ends in DevMode, which makes the build look like a debug build. It is
enabled by the game's own system.cfg and needs no reversing to remove - one CVar switches it off.
The startup logos also render as white rectangles on black, because the x64 build fails to decode
the intro videos. The files themselves are present and Videos.pak is intact, so this is a
decoder problem rather than missing content. Rather than chase the decoder, the launcher skips
the intro.
Both are set from the launcher's command line at startup:
+g_skipIntro 1 +sys_rendersplashscreen 0 +sys_intromoviesduringinit 0 +r_DisplayInfo 0
Pass -keepintro to restore the original behaviour, or set the CVars from the console.
The game loads its cursor with LoadCursorA against the running executable, which with this
project is the launcher rather than Crysis2.exe. The original executable carries those cursor
resources; a launcher built without them makes LoadCursorA return NULL, and the cursor
disappears. The mouse still works: menu buttons highlight on hover, clicks land where they should,
but nothing is drawn under the pointer.
The launcher links the same resources under the same ids the game asks for (103-107, the amber, blue, green, red and white Crysis cursors), and the game's own icon under id 101 - without it Windows draws the blank default, and the launcher looks like a stray tool next to the game rather than a way to start it.
Both are Crytek assets, so they are not stored here. extract_resources.py pulls them out of the
Crysis2.exe of the installed game at build time, and build.ps1 runs it automatically.
Crysis 2 has no 64-bit game executable, so this one takes the place of Crysis2.exe and drives
the engine directly. At startup it:
- Raises the timer resolution to 1 ms, before anything else, which is what lifts the 64 FPS ceiling described above.
- Sets the working directory to the game root, so the engine resolves its own paths.
- Appends console commands to the engine's command line: windowed at desktop resolution for borderless mode, plus skipping the intro and the debug overlay.
- Starts a background thread that finds the game window and strips its frame, then keeps watching in case the engine recreates the window.
- Writes
launcher_diag.txt, before engine init so the file survives a startup crash. - Creates the engine through
CreateSystemInterface, in the same order the editor uses, and hands the result to the game DLL throughSSystemInitParams::pSystem. That hand-off is not optional: the field is documented as "reused if not NULL", and without it the game brings up a secondCSystemon top of the first. The second one dies while allocating the pak heap pools, which surfaces asFailed CMTSafeHeap::m_pBigPool allocationon startup. - Patches the loaded engine DLLs in memory: the CryAction release asserts that force-crash on level load, the CryMovie update loop that walks freed entries after a layer unload, and two unimplemented vtable slots in the editor build of CrySystem.
- Loads the game DLL, calls its entry point, and enters the main loop.
Steps 6 and 7 are ordered deliberately: the patches have to be applied after the modules are loaded but before the game initialises and starts using them.
Command line flags:
| Flag | Effect |
|---|---|
-noborderless |
leave the window alone, use whatever mode the game picks |
-keepintro |
keep the intro videos and the debug overlay |
-allowmultiple |
start a second copy without asking |
-traceallocs |
log large engine allocations to launcher_faults.txt |
-forcehighheap |
force the heap above the 4 GB line, to reproduce truncation on demand |
-noenginefix |
leave the pointer-width corrections out (applied by default) |
-nocbfix |
leave the renderer's constant-buffer cache at its original size |
-noaifix |
let the AI call into a destroyed target |
-nosndfix |
let the sound engine call into a destroyed object |
-nomodfix |
leave the truncating stores in every module's copy of the allocator alone |
-topdown[:KB] |
serve large reservations from above the 4 GB line |
-arenahigh |
serve allocator arenas from above the 4 GB line |
You need:
-
WDK 7.1 for the VC90 x64 compiler. This is not nostalgia: the launcher has to link against msvcr90 as its primary CRT so the process heap lands below the 4 GB line, or retail CrySystem's truncated slab pointers come back corrupt. See the heap section above.
Tested rather than assumed: the same source built with MSVC 14.51, both
/MDand/MT, does not start at all. The process hangs beforeWinMainreaches its first line - noGame.log, nolauncher_diag.txt- becauseCrySystemis imported statically and its static initialisers allocate through the engine allocator while the wrong CRT is the primary one. -
Windows 10 SDK for
rc.exeandmt.exe, the resource compiler and manifest tool. -
An installed copy of the game, because the build extracts the cursors and the icon from its
Crysis2.exe.
Run it:
.\build.ps1The script finds the game by walking up from its own folder, looking for a Bin64\CrySystem.dll
next to game content. If it guesses wrong, or the repository is cloned somewhere else entirely,
name the install yourself:
.\build.ps1 -GamePath "C:\Games\Crysis 2" -NoDeploy| Parameter | Effect |
|---|---|
-GamePath |
the Crysis 2 install, the folder holding Bin64 and gamecrysis2 |
-Wdk |
WDK 7.1 root, if it is not in C:\WinDDK\7600.16385.1 |
-Kit |
Windows SDK bin\<version>\x64, if the newest installed one is not wanted |
-NoDeploy |
build only, do not copy the result into the game |
The script extracts the cursors and the icon, compiles the resource script, builds Main_min.cpp, embeds the
VC90 CRT manifest, and copies the result into the game's Bin64.
Nothing links against the engine. CreateSystemInterface is resolved with LoadLibrary at
startup, so no import library is generated and none is needed - and a missing or misplaced engine
produces an explanation from the launcher rather than Windows' "reinstall the program" box.
The cursor .cur files and the .ico are absent from this repository on purpose and extracted
from the installation at build time: they are Crytek assets, not ours to distribute.
.\test.ps1 -Runs 3It launches the game a few times and reports whether each run actually reached its menu. The verdict matters more than it sounds: a build can stop printing an error and still be broken, so "no error in the log" is not a pass. A run counts only if the renderer came up, the window is a real window rather than the 8x8 one a half-initialised renderer creates, no allocator failure was logged, and the process actually loaded content. Startup is not deterministic either, which is why the default is three runs rather than one.
| Issue | Notes |
|---|---|
| Intro videos render as white rectangles | Skipped by default. The decoder is present and is not the problem - see below. |
| Co-op dialogue lines cut off and repeat | Not game-breaking. |
| The engine reports 0 MB of video memory | Real, but no effect could be measured - see below. |
| Some props do not appear, but still block you | Seen in a few spots (the AA-gun area on Bryant Park, parts of Prism2). The object is there - physics, collision, everything - only its geometry never arrives. Walk on and it does not follow you. |
| A few textures stay unloaded | Same places, same cause. |
| One screen shows the wrong image | The Hargreave monitor on Prism plays something else. |
| A Screamer had no sound once | Not reproduced since. |
The last four come from a full campaign playthrough on this launcher with memory steered high. None of them stop the run; they are listed because you will notice them and they are worth knowing about before you think something is wrong with your installation.
On the videos. It is tempting to assume the 64-bit build has no video decoder. It has one:
retail x64 CrySystem.dll carries a complete 64-bit CRI Sofdec build (CRI Movie/PCx64 Ver.2.68),
together with its error strings - Need to call CriMv::Initialize(), CRI Heap is not initialized, Decode USM header timeout.
More than that, the decoder demonstrably runs. Playing the in-level cutscene on Battery Park
logs PlayVideo, then Disable scene rendering for playback of FMV sequence, then
re-enable scene rendering exactly 71 seconds later - the length of the file - and not one CRI
error in between. Reproduced three times. So whatever is wrong with the intro logos is in
getting a decoded frame onto the screen, not in decoding it, which is a different place to look
than the one this section used to point at.
On the video memory. The renderer reports zero on a card with 12 GB, and follows it with
"Disabling of textures streaming...". That message is printed unconditionally as part of
reinitialising the texture manager - "Finished initializing textures streaming..." follows a few
lines later - so it is not the disable it appears to be. The zero itself is real, but forcing the
size check that depends on it, and setting r_TexturesStreaming 1, both change nothing
observable.
On every start the launcher writes launcher_diag.txt next to the game, before engine init, so
the file exists even if the engine dies on startup. It records:
- the launcher build and the command line it was given
- whether each fix actually applied - the timer, borderless, the engine patch, the allocator trace
- the install path, whether the game folder is writable, free disk space, and the system locale
- OS build, CPU thread count, RAM, desktop mode and refresh rate, GPU name, DPI scale
- how much address space is free below the 4 GB line, and the largest single free block in it
- where the engine's allocator actually placed its first block, and whether a 14 MB request succeeds
- the size and date of every engine module, and the size of every game pak
The write-access and free-space lines exist because a read-only install or a full disk produces a failure that looks like a code bug. The address-space lines exist because this engine only works while its memory stays low, so knowing where it landed is the difference between a guess and an answer.
The pak list matters more than it looks: bug reports that appear to be launcher problems often turn out to be differences between game copies (retail versus a repack with re-encoded or removed files). Comparing pak sizes settles that in seconds instead of a debugging session.
Nothing user-identifying is collected: no user name, no profile paths, no serials, no network information.
The crash testers hit on the heavy levels is not a bug in any one place. It is the 32-bit ceiling arriving: this engine keeps its memory below the 4 GB line, and when the free address space down there runs out, the next allocation fails and the game dies. Nothing in the log points at a cause, because there isn't one - the game simply ran out of room in a place a 64-bit process should not have a limit at all.
The launcher now watches for it. The hooks are installed at startup and do nothing; a check every couple of seconds measures the free address space below 4 GB, and if it drops under 512 MB, allocations from that point on are placed above the line instead:
lowguard: 412 MB free below 4 GB, under the 512 MB mark - new allocations go high from here
Players who never approach the ceiling run exactly as they did before - same addresses, same layout, nothing steered. A level or a mod that would have hit the wall goes past it.
This only works because of the pointer-truncation corrections: the allocator writes 32 bits of a
64-bit pointer in eight places per module, and memory above 4 GB is fatal without them. They are
on by default. -lowguard:MB changes the threshold, -nolowguard turns the watch off, and
-topdown is still there for placing everything high from the start.
A 64-bit executable that never puts a byte above the 4 GB line is 64-bit in file format only. Two flags settle the question, and the answer is on the record rather than assumed.
-topdown places every large allocation at the top of the address space, where losing the upper
half of a pointer is fatal instead of harmless. -memstress:GB is the examination: once a level
is loaded it asks CryMalloc - the retail bucket allocator, the one carrying eight truncating
stores in every module - for that many gigabytes in 4 MB blocks, writes a pattern into every page
and reads all of it back. A pointer that lost its top half cannot survive that: the block gets
written at one address and read at another.
.\launcher64.exe -topdown -memstress:4 +map TimesSquare
memstress: 1024 of 1024 blocks (4096 MB), 1024 above 4 GB, 0 corrupted
census now 1699 MB low / 4823 MB high
750 ms to fill, and the game is still running
Six gigabytes works the same way, with the process past 8.6 GB and the level still playable. The
same run with -noenginefix crashes inside CrySystem before the level loads, which is the
control this needed.
Raising the engine's own pools does not produce that memory on its own - textures at 3072 MB, particle and mesh pools raised, and a level still only wants 2.4 GB. The game does not have that much content. What 64-bit buys is the room to add it.
The census shows around 1.6 GB still under 4 GB, and it is worth saying plainly what that is, because the obvious reading - "the work is a third done" - is wrong.
Every large block the allocation hook sees now goes high: 56 of 56 on a level, with 5 MB of
rounding left low. What remains below is 59 slabs of 8 to 32 MB whose addresses the hook never
handed out. They are fully committed and never written - 0 of 256 probes across each one - and
at least one carries PAGE_WRITECOMBINE, the protection used for memory a GPU writes through.
No module of the game contains a direct syscall stub, so nothing is bypassing the hook in user
mode; NtAllocateVirtualMemoryEx is hooked too and is never called. These are mapped by the
kernel for the display driver, and their address is not ours to choose.
So the honest figure is not "30 percent of memory is high". It is: of the memory the engine asks for itself, what stays below 4 GB is five megabytes.
Catching a crash by hand costs an evening: the game has to be played until it dies, and the interesting ones only show up after an hour. Two scripts do it instead.
soak.ps1 runs levels on its own - it waits for the level to finish loading, moves the player so
the AI and the renderer are not idling at a spawn point, and collects what the launcher recorded
for each level into one table.
.\soak.ps1 the campaign, 17 levels, two minutes each
.\soak.ps1 -Levels TimesSquare -Sec 60 one level
.\soak.ps1 -Extra "-topdown -modfix" with flags
.\soak.ps1 -KeepGoing do not stop at the first crash
It never deletes a log. An earlier version cleared Game.log before each run "to keep things
tidy" and threw away the record of a full campaign playthrough, which was the one thing that
could not be reproduced.
A full campaign run with the corrections on, for reference:
Level Status LoadSec PlaySec AVs
TimesSquare ok 23 71 0
Downtown ok 25 71 0
CentralStation ok 24 71 0
...
Flags added along the way, all off unless asked for:
| Flag | What it does |
|---|---|
-topdown[:KB] |
large allocations to the top of the address space; the exam |
-memstress:GB |
ask the engine's allocator for that much, check every page |
-heaphigh[:KB] |
large heap blocks out of the process heap (548 a level, 1128 MB) |
-topmap |
file views high as well; counted either way |
-memdebug |
the per-event memory diagnostics, which are noisy |
memmap.ps1 answers the other question - whether a running build is really using 64-bit memory:
private below 4 GB : 1645 MB
private above 4 GB : 622 MB
27% of private memory is high
Without -topdown that second number is zero, on every level: the 64-bit build never put a
single byte above the 4 GB line on its own.
- c1-launcher by ccomrade - the original open-source Crysis launcher, and the reference that showed this approach is viable.
- c2-launcher - the CryAction assert-patch addresses used here were taken from that project.
This launcher's source is written from scratch (STL-free, so it can be compiled by the VC90 toolchain); it is not a fork of either project.
MIT - see LICENSE.