Have you ever wished someone made a video game about The Dollars Trilogy? Well, if you have by any chance played Outlaws, it was pretty close to that. The soundtrack, above all, lives up to the films, no doubt. Listen to the main theme:
Outlaws is one of the still rare Old West themed First Person Shooters. Published in 1997 by LucasArts, it was quite a shift of paradigm for the studio, which then had only done games based on existing film IPs (Star Wars, Indiana Jones) and a few emblematic point-and-click adventure games. They took a gamble with an entirely new title and style, and ended up making one of the best western First Person Shooters to date!
Outlaws started as a spin-off of Star Wars: Dark Forces, which was a previous FPS title by LucasArts. It was an evolution of the same technology used before, which was internally called the Jedi Engine. This game engine was similar to the ones used by previous “two and a half D” games like Duke Nukem 3D and Doom, where it featured 3D levels populated with 2D sprites that always faced the player (also known as billboards).
These primitive 3D engines were not based on polygon rasterization, but raycasting and scanline filling
techniques instead, entirely drawn on software with no hardware acceleration. Outlaws used a very polished
version of such rendering engines, supporting very complex levels with multi-storey buildings, underwater
passages, caves and skyboxes, and of course those breakable windows and bottles everyone loved to shoot at
(shame that back then we didn’t have online achievements, otherwise I’d have a “Glass Breaker” badge
Outlaws was released in a transitional time period when games were starting to go full hardware-accelerated 3D. Quake came out almost a year earlier with full 3D worlds, so the graphics in Outlaws left some things to be desired. That probably contributed a bit for the game not becoming a major market success. Nevertheless, for those who played the game at an impressionable age, like I did, it was a fantastic game to sink hours in. I always dreamed about making a fan remake of it, now that I know some stuff about game development, but that’d be a huge project I fear I wouldn’t have the time or resources to undertake. Failing that, doing some reverse engineering on it should also be a lot of fun!
Let’s start out by unpacking the asset files used by Outlaws, see if we can extract a few sprites, textures or such from the game. By the way, Outlaws is available on GoG.com!
If you look into your local install of the game, there should be quite a few files, mostly DLLs
(don’t know why they relied so much on DLLs, every Windows programmer knows how they can be a pain in the neck),
the files we are going to be looking into today are the
.LAB files, which are the archives that group all game assets use by Outlaws.
The LAB file format
LAB files, first four bytes
.LAB, are the LucasArts BiNary archive format.
This is a very straightforward game-package-like uncompressed binary format. In the Windows folder where the
game is installed you should be able to find at least:
outlaws.lab olobj.lab olsfx.lab oltex.lab olweap.lab olpatch1.lab olpatch2.lab
The game was patched several times over the years to add support for D3D and Glide,
so we’ll find archives like
olpatch1.lab in the directory for latter releases.
The file names are more or less self-evident,
olobj has sprites and configurations for game objects,
olsfx has all the WAV sound effects used by the game,
olweap has sprites for the player weapons
(from the player’s perspective), and so on.
An Internet search about the file format should lead to this Xentax Wiki entry, which was the only description I could find online. That’s a start, but trying out the layout described there didn’t work out-of-the-box. After a few hours of testing and looking at the files in the hexadecimal editor I was able to figure out the exact layout. I’ll give Xentax the benefit of doubt, since the format described there might be from an earlier version of the game, but that site is known to have errors, such as I’ve found in the Darkstone MTF compression format.
Following it a sample of the first few KB of
A LAB archive is divided into four sections, in this order:
- A tiny LAB archive header;
- A list of file entry headers for each file inside the LAB archive;
- A list of null-separated ASCII strings with each file name for the entries;
- Data for the individual file entries, tightly packed together with no padding in between files.
In the above hex dump, the highlighted block is the list of file names, which precedes the data for each file in the archive. The following C++ structures for the headers should give a good idea of how the format is laid out, it is really simple, just an amalgamate of a bunch of files, with minimal metadata right at the beginning, to save as much space as possible:
As you can see above, the
LabFileEntry header, which is how we can seek and find a given
file in the blob of data, doesn’t have a file name. Instead, it has an offset into the list
of null-separated strings that follow the list of entry headers (
nameOffset). This is the
only peculiar bit about this format, apart from that it is pretty standard and easy to reverse.
Actually, it is so simple that I went ahead and wrote both an unpacker and a re-packer for LAB archives. You can find them in the source code repository (command line tools, so don’t expect a GUI).
I haven’t tested LABs generate by my tool in the game, but
diffing them with the originals seem to
produce identical files, except that sometimes the order of file entries in the archive might be different
from the original, but that should be irrelevant as far as the game is concerned. Hopefully, these simple
tools can be of use to some eager modder out there still interested in the game. Shout out if you are using
the tools for something and would like to have more features added to them!
What’s in the LAB?
Once a LAB archive is opened, we can find a myriad of other custom formats use by the game.
Surprisingly, Outlaws made heavy use of plain text file formats, which is not very usual
in games, which tend to prefer binary for the speed and ease of loading. Most object config
files and such are plain text, following a somewhat standard layout. For example, an
ITEM 1.0 NAME Barrel01 FUNC Inv_Object ANIM Barrel01.nwx DATA 3 FLOAT RADIUS 1.0 FLOAT HEIGHT 3.0 STR TYPE SHOOT
The text files mostly follow this
KEY VALUE notation, so they probably had a common lexer
tool in the engine for quickly parsing these files. Pretty neat! That makes life a lot easier
for modders and hackers like me
The “physical” properties of objects were described by
.phy files, such as in the following
Judging by the names of the keys, some parts of the game made use of Euler Angles.
PHYS 1.0 ACCEL X: 100.0 Y: 50.0 Z: 100.0 DECEL X: 20.0 Y: 120.0 Z: 20.0 MAX_VEL: 15.0 JUMP_VEL: 20.0 STEP_HEIGHT: 3.0 STEP_SPEED: 20.0 BODY_YAW_RATE: 150.0 HEAD_YAW_RATE: 500.0 HEAD_YAW_LOOK_BACK_RATE: 800.0 HEAD_YAW_RETURN_RATE: 300.0 PITCH_RATE: 160.0 OFF_GROUND_ABILITY: 0.9
Level geometry textures and UI textures are stored in the old ZSoft PCX format. You can open them in a tool like Gimp. Most will look like random noise if you open them, but that’s not an error in the extractor. Most of the images are actually palette textures (AKA colormaps), so they don’t contain a whole image, but a pool of reusable pixels other images can use to fetch color from. Palettized textures with shared palettes is one of the oldest forms of lossy image compression used by games.
The sprites for the 2D billboards like the badies and player weapons are stored in a custom binary
format with the
.NWX extension. This format is not terribly complex, it seems to consist of a
set of uncompressed bitmaps with metadata about each frame of animation. There’s a Windows tool called
“Nwx Editor” that allows editing those, you can find it at theoutlawdad.com. I didn’t
test this tool, but the interesting thing is that the zip package I downloaded in the previous link
also contained a doc with a partial description of the binary format, so maybe I’ll look into writing
an NWX editor in the near future…
Trivia: The original Outlaws came in a set of two CDs. The CDs not only contained the game but were also playable in a standard CD Player so that you could listen to the game soundtracks! I do not know of any other game that has done that. Such an awesome perk, since the soundtracks are some of the coolest things about this game. If you still have your old Outlaws CDs, you can now open them in a CD rip tool to extract the musics.
Where are you Marshal?!?
I mean, where are the game levels?
What would really be cool is to write a modern OpenGL renderer for the game levels,
and I might do that next, as long as I manage to figure out the layout of the
.LVB binary format. LVB, I assume, is short for Level Binary. This file format would
otherwise be very hard to reverse engineer if it wasn’t for the existence of a sibling
.LVT variant (Level Text) format to use as a base. Actually, in this version of the game
I’m looking at there’s only one lucky LVT file sample, without which I would probably have
given up reversing the LVB format. The LVT text variant follows a similar structure to the other
text files used by the game, so its is pretty easy to parse, the hard part though is
making sense of the data. Like I mentioned in the beginning, the 3D elements of the game
are not defined in the now usual sense of polygons or triangles. Levels were comprised
of “walls” and “sectors”, so converting that to renderable geometry is a little tricky.
Based on the LVT, I’ll try to figure out the LVB format in the next few weeks and see were it leads,
so stay tuned for more
:P. The really cool thing to do would be to actually write a raycast
engine and draw the game levels “the proper way”, but I’ll probably cheat and just find a way of
converting the data to OpenGL triangles. Maybe after that, if I’m still up for it, I’ll give
a raycaster a try and see if I can draw the Outlaws maps in the same way the game did.
Bonus track: A peek into the code
One thing that saddens me as a game programmer is that source code for my favorite classics will never be made Open Source. The brutal reality is that most of these games were made in a time when source code repositories and online storage barely existed, so in the vast majority of cases the source code simply got lost forever when companies closed or got acquired by others. It is very possible that the only copies of source code for most of the LucasArts classics ended up in some dumpster inside the hard-drive of an old IBM PC.
The closest we can get from looking at the source code today is by disassembling the remaining executables. That unfortunately is nowhere near looking at the original code, but, looking into the executable of Outlaws did yield a nice surprise.
outlaws.exe through the
strings utility trying to filter any printable strings
from the binary. It turns out that the game had a few instances of what appears to be hardcoded
log calls that had function names in them for tracing. Those log calls made into the release
build, maybe by mistake. Following is an excerpt, we can get a feel for the style of the code
and spot the names of the key systems of the game and Jedi Engine. The game was probably written mostly in C.
Function names apparently from log calls in the game code:
Actor_SysInitialize: Attempt to reinitialize actor module Atx_SysInitialize: Attempt to reinitialize Atx system CmmLex_SysInitialize: Attempt to reinitialize CmmLex system Cmm_SysInitialize: Attempt to reinitialize Cmm system Collide_SysInitialize: Attempt to reinitialize Collide system Color_SysInitialize: Attempt to reinitialize Color system Config_SysInitialize: Attempt to reinitialize Config system Control_SysInitialize: Attempt to reinitialize Control system DB_SysInitialize: Attempt to reinitialize DB system DLL_SysInitialize: Attempt to reinitialize DLL system Dbm_SysInitialize: Attempt to reinitialize Dbm system DeadReck_SysInitialize: Attempt to reinitialize DeadReck system Display3D_SysInitialize: Attempt to reinitialize Display3D system Display_SysInitialize: Attempt to reinitialize Display system Drivers_SysInitialize: Attempt to reinitialize Drivers system Elevator_SysInitialize: Attempt to reinitialize Elevator system Enemy_SysInitialize: Attempt to reinitialize Enemy system FX_SysInitialize: Attempt to reinitialize FX system FileUtil_SysInitialize: Attempt to reinitialize FileUtil system Gen_SysInitialize: Attempt to reinitialize Gen system Graph_SysInitialize: Attempt to reinitialize Graph system IFace_SysInitialize: Attempt to reinitialize IFace system Inf_SysInitialize: Attempt to reinitialize Inf system Info_SysInitialize: Attempt to reinitialize Info system Inv_SysInitialize: Attempt to reinitialize Inv system Item_SysInitialize: Attempt to reinitialize Item system Jedi_SysInitialize: Attempt to reinitialize Jedi system Kbd_SysInitialize: Attempt to reinitialize Kbd system Level_SysInitialize: Attempt to reinitialize Level system List_SysInitialize: Attempt to reinitialize List system Logic_SysInitialize: Attempt to reinitialize Logic system Map_SysInitialize: Attempt to reinitialize Map system MemVirt_SysInitialize: Attempt to reinitialize MemVirt system Memory_SysInitialize: Attempt to reinitialize Memory system Misc_SysInitialize: Attempt to reinitialize Misc system Module_SysInitialize: Attempt to reinitialize Module system Module_SysInitialize: Attempt to reinitialize PCX system Movie_SysInitialize: Attempt to reinitialize Movie system MuScript_SysInitialize: Attempt to reinitialize MuScript system Music_SysInitialize: Attempt to reinitialize Music system NetMsg_SysInitialize: Attempt to reinitialize NetMsg system Network_SysInitialize: Attempt to reinitialize Network system Node_SysInitialize: Attempt to reinitialize Node system Obj3DO_SysInitialize: Attempt to reinitialize Obj3DO system Path_SysInitialize: Attempt to reinitialize Path system Physics_SysInitialize: Attempt to reinitialize Physics system Platform_SysInitialize: Attempt to reinitialize Platform system Player_SysInitialize: Attempt to reinitialize player system RLE_SysInitialize: Attempt to reinitialize RLE system Rcp_SysInitialize: Attempt to reinitialize Rcp system Res_SysInitialize: Attempt to reinitialize Resource system Router_SysInitialize: Attempt to reinitialize Router system Screen_SysInitialize: Attempt to reinitialize Screen system Sector_SysInitialize: Attempt to reinitialize Sector system Server_SysInitialize: Attempt to reinitialize Server system Shot_SysInitialize: Attempt to reinitialize Shot system Slope_SysInitialize: Attempt to reinitialize Slope system Sound_SysInitialize: Attempt to reinitialize Sound system Symbol_SysInitialize: Attempt to reinitialize Symbol system Task_SysInitialize: Attempt to reinitialize Task system Term_SysInitialize: Attempt to reinitialize Term system Text_SysInitialize: Attempt to reinitialize Text system Texture3D_SysInitialize: Attempt to reinitialize Texture3D system Tokenize_SysInitialize: Attempt to reinitialize Tokenize system Trigger_SysInitialize: Attempt to reinitialize Trigger system Wall_SysInitialize: Attempt to reinitialize Wall system Wav_SysInitialize: Attempt to reinitialize Wav system Wax_SysInitialize: Attempt to reinitialize Wax system Weapon_SysInitialize: Attempt to reinitialize Weapon system World_SysInitialize: Attempt to reinitialize World system