See Dependencies first.
-
Clone the repository.
-
Place the original game executable and its DLLs in
orig/<VERSION>/. The path insideorigmatches the version ID inconfigure.py.orig/BW1W100/runblack-decrypted.exe— Windows v1.0 (decrypted)orig/BW1W110/runblack-decrypted.exe— Windows v1.1 (decrypted)orig/BW1W120/runblack-decrypted.exe— Windows v1.2 (decrypted)orig/BW1W130/runblack-decrypted.exe— Windows v1.3 (decrypted)orig/BW1WCI/CreatureIsle-decrypted.exe— Windows Creature Isle (decrypted)
The Windows builds also need the four DLLs shipped alongside the exe, in the same directory:
LHaudiodllR.dll,LHLogR.dll,LHMultiplayerR.dll,LHDialogLib.dll(copy these from your own install — the build checks all five files' hashes againstconfig/<VERSION>/build.sha1and fails at the split step with a clear "not found" message naming whichever one is missing).The Windows builds expect a decrypted exe. The original retail discs ship the executable wrapped in SafeDisc 2 / Macrovision protection. Decrypting it is currently undocumented tribal knowledge — if you don't already have a decrypted copy, ask in the project's Discord/issue tracker rather than guessing at a tool name.
-
Place the MSVC 6.0 static CRT libraries (and Visual Studio .NET 2002's
libcmt.lib) and the DirectX 7.0 SDK headers underorig/(they are not committed and not downloaded).BW1W120 was built with VC6 SP5; BW1W100/BW1W110 with VC6 SP4 (confirmed via Rich header build-number comparison — see
configure.py). BW1W130 and BW1WCI use BW1W120's libraries.configure.pypicks the right package automatically based on--version, so both sets need to be present side by side if you plan to work on more than one version.MSVC 6.0 SP5 static libs →
orig/libs/msvc6.5/(BW1W120)Needed:
libcmt.libandlibcpmt.lib. If you have a Visual C++ 6.0 (SP5) install, copy them straight fromVC98\Lib. Otherwise pull them from a plain ISO of the SP5 disc on the Internet Archive, itemMSDN_VisualStudio_6.0_SP5_MASM_6.11_Visual_C_1.2(VS60SP5_EN, byte-verified identical to the genuine SP5 CD):- Download
MSDN_VisualStudio_6.0_SP5_MASM_6.11_Visual_C++_1.2.isofrom that item. It's a plain ISO9660 image — no proprietary disc-image format, no conversion step. - Extract
ENGLISH/VS60SP5/VC98/LIB/LIBCMT.LIBandLIBCPMT.LIBwith7z e <iso> "*LIBCMT.LIB" "*LIBCPMT.LIB" -r, or mount the ISO directly (mount -o loopon Linux; double-click on Windows/macOS). - Copy the two files to
orig/libs/msvc6.5/libcmt.libandorig/libs/msvc6.5/libcpmt.lib. Filenames are matched case-insensitively; lowercase is fine.
MSVC 6.0 SP4
libcmt.lib→orig/libs/msvc6.4/(BW1W100/BW1W110)From the Internet Archive item
msdn-disc0034-january-2001-x06-11596(a plain ISO, no disc-image conversion needed):- Download
1_V60SP4_DE.iso. - Extract
GERMAN/VS60SP4/VC98/LIB/LIBCMT.LIBwith7z e <iso> "*LIBCMT.LIB" -r, or mount directly. - Copy it to
orig/libs/msvc6.4/libcmt.lib.
MSVC 6.0 RTM
libcpmt.libandlibcimt.lib→orig/libs/msvc6.0/(libcpmt.libfor BW1W100/BW1W110,libcimt.libfor BW1W110/BW1W120/BW1W130)SP4 never shipped its own
libcpmt.lib— checked three independent SP4 discs, none include it, since Microsoft only redistributes files that changed for a given service pack. The original 1998 RTM copy is the right one (a real SP4-era machine would still have it, untouched, inVC98\Lib). It's byte-different from SP5's, so don't substitute that one.libcimt.lib— the pre-standard iostreams (ios,streambuf,filebuf,fstream,istream,ostream), which the game uses alongside the standard library inlibcpmt.lib— is a stronger case of the same thing: no service pack ever replaced it. The SP5 package shows this plainly, every library in it dated 2000-08 exceptlibcimt.lib, still the 1998-06-17 original. So RTM's copy is what 1.10 and 1.20 both link, and it is the only one that matches: a later rebuild (Feb 2004, SP6-era) exists in the wild whose_ios.objdiffers — itsiosconstructors are 0x64/0x6F/0x4D/0x51 bytes against RTM's 0x5E/0x69/0x48/0x4A, and it adds an__InitLockclass and a.CRT$XCLstatic initializer that appear nowhere in either game binary. If your copy is dated 2004, it is the wrong one.Both come off the same disc, from the Internet Archive item
visualstudiov60enterpriseedition_199807:- Download Disc 1 (
...(X03-78941)(Microsoft Corporation)(August 1998).iso, a plain ISO). - Extract
VC98/LIB/LIBCPMT.LIBandVC98/LIB/LIBCIMT.LIBwith7z e <iso> "*LIBCPMT.LIB" "*LIBCIMT.LIB" -r, or mount directly. - Copy them to
orig/libs/msvc6.0/libcpmt.libandorig/libs/msvc6.0/libcimt.lib.
Visual Studio .NET 2002
libcmt.lib→orig/libs/msvc7.0/(BW1W120/BW1W130)LHLogR.dllwas rebuilt for 1.20 with Visual Studio .NET 2002 and links that version's static CRT (its Rich header listsUtc13_C9466 and masm 9162 objects); 1.30 ships the same DLL. If you have a Visual Studio .NET 2002 install, copyVc7\lib\libcmt.lib. Otherwise take it from the Internet Archive itemX08-65717(Microsoft Visual Studio .NET 2002 Professional Edition, Disc 1, RTM, January 2002):-
Download
2002-02_X08-65717_X08-65353_VSPROD1.7z(311497891 bytes, md5c1763dcd16f5e8b03cf3ac844e17175f) and extractimage/VSPROD1.mdffrom it with7z e 2002-02_X08-65717_X08-65353_VSPROD1.7z image/VSPROD1.mdf. -
The image is an Alcohol 120% MDF that 7z cannot read as is: raw 2448-byte sectors (2352 bytes of Mode 1 data plus 96 of subchannel), each holding its 2048 bytes of user data at offset 16. Convert it to a plain ISO:
python3 -c "import sys;f=open(sys.argv[1],'rb');o=open(sys.argv[2],'wb');[o.write(s[16:2064]) for s in iter(lambda:f.read(2448),b'')]" VSPROD1.mdf VSPROD1.iso -
Extract
Program Files/Microsoft Visual Studio .NET/Vc7/lib/libcmt.libwith7z e VSPROD1.iso "Program Files/Microsoft Visual Studio .NET/Vc7/lib/libcmt.lib", or mount the ISO directly. -
Copy it to
orig/libs/msvc7.0/libcmt.lib. Its SHA-1 isc4c2b278697318c7289f50a73faf7cd47d01814e.
DirectX 7.0 DDK →
orig/directx7.0/From the Internet Archive item
dx7ddk:- Download
dx7ddk.exe(a self-extracting installer). - Extract it with 7-Zip (
7z x dx7ddk.exe), or run it on Windows to install. - Copy both the
includedirectory (holdingddraw.h,d3d.h,dinput.h,dsound.h, …) and thelibdirectory (holdingddraw.lib,dinput.lib,dsound.lib,dxguid.lib, …) toorig/directx7.0/include/andorig/directx7.0/lib/.
Intel Approximate Math Library (AM Library) →
orig/libs/amaths-2.0/Needed:
amaths.liband its headerAMaths.h. The game links Intel's AM Library verbatim (SSE/SSE2sin/cos/sincos/tan/atan/exp/log/pow), Release 2.0 (Oct/Nov 2000). Intel's own download location is long gone; pull the package from the Wayback Machine:- Fetch
AMaths.zipfrom the Internet Archive capture of Intel's old Pentium III devtools page:https://web.archive.org/web/20060218064543id_/http://www.intel.com/design/pentiumiii/devtools/AMaths.zip(theid_suffix returns the raw file instead of a replayed page). - Unzip it. It contains
AMaths.lib, the headerAMaths.h,AMaths.c(source, for reference),AMaths.pdf(documentation),MathTest.exe/.cpp(Intel's test harness), and license files. - Copy
AMaths.libtoorig/libs/amaths-2.0/amaths.lib. As with the MSVC libs, the filename must be lowercase to match the config key. - Copy
AMaths.htoorig/libs/amaths-2.0/AMaths.h, keeping its name as it is:LH3DP3.cppincludes it.
Intel C++ 5.0
libircmt.lib→orig/libs/icc-5.0.115/(BW1W110/BW1W120/BW1W130)Needed:
libircmt.lib, the multithreaded Intel C++ runtime. One member is linked,cpu_disp_mt.obj— the CPU dispatch code (___intel_cpu_indicatorand its init) thatLH3DP3.cpp, the one translation unit built withicl, drags in.Both versions link the 5.0.1 beta, build 010214Z, which has not turned up anywhere. The nearest release, 5.0.115 (build 010525Z), is close enough to use: its
cpu_disp_mt.objhas the same section layout and differs only in ten code bytes across nine sites (a stack adjust, and the[ebp-4]/[ebp-8]locals swapped) plus the compiler banner in the.drectve.tools/post_link_patch.pyrewrites both, so the image still hashes clean — delete those two patches if a 010214Z copy ever surfaces.The compiler is downloaded automatically (see below) and ships the libraries with it, so the simplest source is
build/compilers/ICC/5.0.1/lib/:- Run
ninjaonce so the compiler is fetched. - Copy
build/compilers/ICC/5.0.1/lib/libircmt.libtoorig/libs/icc-5.0.115/libircmt.lib, lowercase as above.
Failing that, it can be extracted from Intel C++ Compiler 5.0.115 install media, where the libraries sit in the InstallShield cabinet
compiler/icl/data1.cab:unshield -g "Compiler32 Lib files (shared)" -d <out> x <media>/compiler/icl/data1.cab- That yields
libirc.lib,libircmt.lib,libm.lib,libmmd.lib,libmmt.libandsvml_disp.lib. Onlylibircmt.libis needed; it should holdpsa_mt,dispfail_mt,cpu_disp_mt,xmm_malloc_mt,cpcheck_mtandpgopti_mt. - Copy it to
orig/libs/icc-5.0.115/libircmt.lib, lowercase as above.
QSound QMixer 5.00 SDK →
orig/libs/qmixer-5.00/Needed: the header
Qmixer.h, whichLHaudiodllR.dll's sources include for theQMixer.dllthey import. The game ships QMixer.dll 5.00, and the only 5.00 SDK left is QSound's own download, captured by the Wayback Machine:- Fetch
qm500sdk.exe:https://web.archive.org/web/20010429011901id_/http://www.qsound.com:80/gamedev/qmdx-files/qm500sdk.exe(SHA-12dcd6c29ba0a11ee9a9558b2c96a49b7c89f5dae). - It is an InstallShield 3 installer: run it on Windows, or under Wine,
and take the files from the
qmixerdirectory it installs.7z xonly unpacks the outer layer, leaving the files compressed insideqmsdk.z. - Copy
Qmixer.htoorig/libs/qmixer-5.00/Qmixer.h, keeping its name as it is:LHAudioPCH.hincludes it. Its SHA-1 isdcd23c08d5ee425aa51e67aa6a43f19871e5d707. - Optionally copy
QMixer.libalongside it (SHA-16174df13dd1d181080848047151e3a955da26754). The build does not link it, since imports come from the split.idata, and it is not the exact library Lionhead linked. That one also exportedQSWaveMixGetWaveLength, like the shipped QMixer.dll, so 21 of the DLL's 32 QMixer import hints are one higher than this library's.
- Download
-
Run
configure.pywith the version you want to work on:python configure.py --version BW1W100
Pass
--version BW1W100,--version BW1W110,--version BW1W130or--version BW1WCIfor the other targets. Default isBW1W120. -
Build:
ninja
The first build downloads
dtk,lld-link, and the MSVC 6.0 compilers, then runsdtk pe split(ordtk pef splitfor Mac) to produce per-function.ofiles, links them, post-patches the result, and verifies the SHA-1 againstconfig/<VERSION>/build.sha1.
If the SHA-1 check passes, the rebuilt binary is byte-identical to the original.
Each version lives under config/<VERSION>/:
config.yml— project config, points at the original binary and thesplits.txt/symbols.txtfiles for that versionsplits.txt— assigns address ranges to source files (seesplits.txt)symbols.txt— names and types every symbol in the binary (seesymbols.txt)build.sha1— expected SHA-1 of the original (and, eventually, the rebuilt) binary
- Pick a function from the disassembly that you want to match.
- Add a
splits.txtentry assigning its address range to a source file. - Add a
configure.pyObject(NonMatching, "...")entry underconfig.libsfor that source file. - Write the C/C++ source in
src/until objdiff shows the compiled.omatches the disassembled.o. - Flip the entry to
Object(Matching, "...")and rebuild — the function is now linked from your source instead of the auto-generated split.
Run python configure.py progress (or ninja progress) to print the current code/data match percentage.