The aim of this repository is to collect enough of the information out there to eventually allow the use of the .eal files coming with Creative's EAX-Patches in later patches of Unreal Tournament.
There are two main goals:
-
understand Creative's EaxMan.dll so it can be used in Unreal Tournament v469+
-
create a platform independent alternative implementation to EaxMan.dll
In 2001, Creative released a patch that added level specific EAX presets. The website for that patch is gone now, but it was archived here: EAX enhancements for Unreal Tournament
Here is a demonstration how it sounds with the patch applied: Unreal Tournament's Special EAX Version: Deck16 Demo - YouTube
Those advanced sound effects needed sound hardware supporting EAX (Environmental Audio Extensions - Wikipedia). So the sound effects were applied in hardware and needed the DirectSound3D API which was part of DirectX from version 3 up to version 7.
Starting with Windows Vista, Microsoft deprecated DirectSound3D which meant a loss of hardware acceleration and therefore the EAX effects are no longer accessible.
To restore the hardware acceleration, Creative release ALChemy. This translates the calls to DirectSound3D into hardware accelerated OpenAL calls. The downside is you must have specific sound hardware. The list of supported hardware is limited to some Creative Sound Blaster cards.
When OpenAL was developed, it added the Effects Extension (EFX), which enables EAX calls to be processed in OpenAL. Eventually, Creative ceased the OpenAL development, but a software implementation of OpenAL is still actively developed: OpenAL Soft is a software implementation of the OpenAL 3D audio API. OpenAL Soft implements the EFX extension in software, which makes it independent of specific audio hardware. Therefore the effects can be used with any sound card.
To make use of EFX, the game needs to use OpenAL, which the Windows version of Unreal Tournament v436 does not. The Creative EAX patches only run on this version. They include a modified Galaxy.dll, which does not use OpenAL. But it can use the DirectSound3D API. As we've learned, it does not work under Windows Vista and newer versions of Windows. But the maintainer fo OpenAL Soft also created a DSOAL, a translation layer from DirectSound to OpenAL. With that, the DirectSound3D calls from the game can e processed by OpenAl Soft. So the EAX effects can be used on a modern Windows.
The EAX presets from Creative's EAX patches can be used on modern computer, so why not stop there? The answer is the limitations.
The shortcomings of the solution presented above is its limited scope. The two main limitations are:
-
the EAX patches can only be used in v436. The patched Galaxy.dll does not work with the modern OldUnreal/UnrealTournamentPatches
-
the EAX patches only work on Windows, so there's no way to enable those EAX presets on other OS
The EAX patches for Unreal Tournament are still available today. Several mirrors exist, for example ut-files or OldUnreal or medor or through the Archive mentioned above.
The patches are 3 separate files. The content of each file when installed:
eaxutsys.exe
UnrealTournament\System\EaxMan.dll
UnrealTournament\System\Galaxy.dll
uteaxdm.exe
UnrealTournament\Eal\DM-Barricade.eal
UnrealTournament\Eal\DM-Codex.eal
UnrealTournament\Eal\DM-Conveyor.eal
UnrealTournament\Eal\DM-Curse][.eal
UnrealTournament\Eal\DM-Deck16][.eal
UnrealTournament\Eal\DM-Fetid.eal
UnrealTournament\Eal\DM-Fractal.eal
UnrealTournament\Eal\DM-Gothic.eal
UnrealTournament\Eal\DM-Grinder.eal
UnrealTournament\Eal\DM-Hyperblast.eal
UnrealTournament\Eal\DM-Kgalleon.eal
UnrealTournament\Eal\DM-Liandri.eal
UnrealTournament\Eal\DM-Morbias][.eal
UnrealTournament\Eal\DM-Morpheus.eal
UnrealTournament\Eal\DM-Oblivion.eal
UnrealTournament\Eal\DM-Peak.eal
UnrealTournament\Eal\DM-Phobos.eal
UnrealTournament\Eal\DM-Pressure.eal
UnrealTournament\Eal\DM-Pyramid.eal
UnrealTournament\Eal\DM-Stalwart.eal
UnrealTournament\Eal\DM-StalwartXL.eal
UnrealTournament\Eal\DM-Tempest.eal
UnrealTournament\Eal\DM-Turbine.eal
UnrealTournament\Eal\DM-Zeto.eal
uteaxctf.exe
UnrealTournament\Eal\CTF-Command.eal
UnrealTournament\Eal\CTF-Coret.eal
UnrealTournament\Eal\CTF-dreary.eal
UnrealTournament\Eal\CTF-EternalCave.eal
UnrealTournament\Eal\CTF-Face.eal
UnrealTournament\Eal\CTF-Gauntlet.eal
UnrealTournament\Eal\CTF-LavaGiant.eal
UnrealTournament\Eal\CTF-Niven.eal
UnrealTournament\Eal\CTF-November.eal
The *.eal files are Environmental Audio Library files. Those are installed to the newly created folder UnrealTournament\Eal. Each file contains the EAX presets for the according map.
The Galaxy.dll installed to UnrealTournament\System replaces the existing file. This is the patched Galaxy Audio system that must be used for the EAX presets to work.
The EaxMan.dll is the EAXManager. It is used to parse the .eal files.
The .eal files are created and edited in the Environmental Audio Graphical Librarian Editor (EAGLE™) by Creative. There is no longer an official download, but it has been archived.
To run EAGLE™, DSOAL is needed on modern computers.
EAGLE™ can be used to confirm and understand the information read from .eal files. But it's greatest potential lies in the Import Plugin-In SDK which might be a way to understand the geometry data stored inside .eal files.
There is official specification for Environmental Audio Library files. But it can be decoded: The container format of the .eal files is little endian RIFF - Resource Interchange File Format.
.eal files consist of the main RIFF chunk with the form type eam , some subchunks with global data and 4 LIST Subchunks.
| FourCC | data | shown in EAGLE |
|---|---|---|
| 'majv' | 4 [fixed] | |
| 'minv' | 4 [fixed] | |
| 'exep' | <string> [260 bytes] |
Executable (full path) in Execute Project dialog |
| 'cmds' | <string> [260 bytes] |
Command (parameters) in Execute Project dialog |
| 'gdfm' | <int32> MaxAttenuation; <float> LFRatio; <int32> AngleMaxAttenuation |
Global Diffraction Model |
| 'lisa' | <struct> LISTENERATTRIBUTES [defined in EaxMan.h] |
Listener Attributes |
| 'denv' | <struct> EAXLISTENERPROPERTIES [defined in eax.h] |
Default Environment |
| 'dsrc' | <struct> SOURCEATTRIBUTES [defined in EaxMan.h] |
Default Source |
| 'dfil' | <string> [260 bytes] |
Wave file (full path) in Default Source Properties |
| 'dmat' | <struct> MATERIALATTRIBUTES [defined in EaxMan.h] |
Default Obstacle |
This is a LIST chunk with FourCC envp. The subchunks are:
| FourCC | data | shown in EAGLE |
|---|---|---|
| 'num ' (incl. trailing space) | <int32> |
total number of Environment Models |
| 'nams' | array of <string>; length of each string: 32 bytes |
entries are shown under Environment Models |
| 'lisp' | array of <struct> EAXLISTENERPROPERTIES |
dialog Environment Controls shows data for selected environment model |
This is a LIST chunk with FourCC srcp. The subchunks are:
| FourCC | data | shown in EAGLE |
|---|---|---|
| 'num ' (incl. trailing space) | <int32> |
total number of Source Models |
| 'nams' | array of <string>; length of each string: 32 bytes |
entries are shown under Source Models |
| 'fils' | array of <string> [260 bytes] |
Wave file (full path) in selected source model properties |
| 'srca' | array of <struct> SOURCEATTRIBUTES |
dialog Source Controls shows data for selected source model |
This is a LIST chunk with FourCC matp. The subchunks are:
| FourCC | data | shown in EAGLE |
|---|---|---|
| 'num ' (incl. trailing space) | <int32> |
total number of Obstacle Models |
| 'nams' | array of <string>; length of each string: 32 bytes |
entries are shown under Obstacle Models |
| 'mata' | array of <struct> MATERIALATTRIBUTES |
dialog Obstacle Controls shows data for selected obstacle model |
This is a LIST chunk with FourCC gemp. The subchunks are:
| FourCC | data | shown in EAGLE |
|---|---|---|
| 'num ' (incl. trailing space) | <int32> |
total number of Geometry Sets |
| 'nams' | array of <string>; length of each string: 32 bytes |
entries are shown under Geometry Sets |
| 'fils' | <string> [260 bytes] |
Mesh file (full path) in selected geometry set |
| 'gema' | TODO - not decoded yet |
EAGLE™ uses both .eal and .eam files when editing the geometry of a project. The geometry data is saved twice, once in each file, but in different forms. All objects created in EAGLE™ are saved in an unprocessed way into a .eam file. If you want to continue editing your project in EAGLE™, a .eam file is needed. The structure of a .eam file is described in the next paragraph.
Inside the .eal file, only processed geometry data is stored inside the 'gema' chunk. Everything needed for fast geometry calculations is there. The 'gema' chunk is yet to be decoded fully.
Just as with .eal files, there is no official specification available for .eam Environmental Audio Mesh files. According to the EAGLE™ documentation, a .eam file contains all the data from a .eal Geometry Set, so that it can be further processed using EAGLE™.
.eam files consist of the main RIFF chunk with the form type eam and the following Subchunks:
| FourCC | data |
|---|---|
| 'majv' | 2 [fixed] |
| 'minv' | 1 [fixed] |
The FourCC of this chunk is vind. It contains the index of each vertex.
| data type | description |
|---|---|
<int32> |
total number of indices |
array of <int32> |
index of each vertex |
The FourCC of this chunk is vert. It contains the x,y,z-coordinates of each vertex.
| data type | description |
|---|---|
<int32> |
total number of vertices |
array of <struct> containing: |
vertices[vertex_count][3] |
<float32> |
x-coordinates of a vertex |
<float32> |
y-coordinates of a vertex |
<float32> |
z-coordinates of a vertex |
The FourCC of this chunk is poly. It contains the x,y,z-coordinates of each vertex.
| data type | description |
|---|---|
<int32> |
total number of polygons |
array of <struct> containing: |
|
<int32> |
vertex count in this polygon |
<int32> |
index with step size of vertex count above, so possibly an byte offset? |
<int32> |
-1 [always] |
<int32> |
-1 [always] |
<int32> |
0 [always] |
<float32>[vertex_count] |
not yet decoded |
The FourCC of this chunk is plth. It corresponds to the plane thickness setting in EAGLE™.
| data type | description |
|---|---|
<float32> |
plane thickness |
The FourCC of this chunk is prto.
| data type | description |
|---|---|
<int32> |
not yet decoded |
The FourCC of this chunk is clpl.
| data type | description |
|---|---|
<int32> |
total number |
<int32>[] |
indices not yet understood |
The FourCC of this chunk is bspp. It is an array of nodes.
| data type | description |
|---|---|
<int32> |
number of nodes |
array of <struct> containing: |
|
<int32> |
index of current node |
<int32> |
index not yet understood |
<int32> |
index not yet understood |
The FourCC of this chunk is brsh. It has not been decoded yet.
| data type | description |
|---|---|
| ? | ? |
The FourCC of this chunk is tags. It contains the environment tags/subspace names in EAGLE™.
| data type | description |
|---|---|
<int32> |
total number of environment tags |
<string> [32 bytes] |
environment name (max. 31 characters) |
<float32> |
x-coordinates of environment tag |
<float32> |
y-coordinates of environment tag |
<float32> |
z-coordinates of environment tag |
<int32> |
not yet decoded possibly flags field |
<int32> |
not yet decoded possibly RGB code |
The FourCC of this chunk is srcs. It contains the source tags in EAGLE™.
| data type | description |
|---|---|
<int32> |
total number of source tags |
<string> [32 bytes] |
source name (max. 31 characters) |
<float32> |
x-coordinates of source tag |
<float32> |
y-coordinates of source tag |
<float32> |
z-coordinates of source tag |
<int32> |
not yet decoded possibly source index |
The EAXManager SDK provides us with a reference documentation.
The EAXManager header file from the EAXManager SDK is included in this repository: inc/EaxMan.h.
EaxMan.h depends on EAX 2.0. All necessary files are in the EAX 2.0 SDK. The needed header is included in this repository: inc/eax.h.
Included in this repository in src/ReadEalFiles is a little test program that uses all EAXManager API calls exposed by the header and documented in the reference documentation.
The source code is written in C++20 using Visual Studio C++ 2026. The main use of the code is to learn the behavior of EaxMan.dll so a proper implementation is possible. The tool does not have any purpose other than that, so quality standards like proper error handling are only done in a very primitive and incomplete way.
There are example tools coming with the EaxManager SDK which are more complete and useful for a proper implementation.
Using both methods (EaxMan.dll and EAGLE™) alternately to read the .eal files leads to a good understanding. It is also a test if the use of EaxMan.dll is properly implemented.
The tool has two modes:
ReadEalFiles.exe <filename>.eal
analyses the .eal file with both EaxMan.dll and by a manual analysis and compare both results. See Goal 2.
All static data that can be read out with EAXManager API calls are retrieved. The according API calls are:
GetListenerAttributes
GetSourceAttributes
GetMaterialAttributes
GetEnvironmentAttributes
The most relevant call is the last one as it provides all parameters for setting up the Listener properties.
An example out looks like this:
> ReadEalFiles.exe CTF-Gauntlet.eal
analyzing file manually: CTF-Gauntlet.eal ... success
analyzing file with EaxMan.dll: CTF-Gauntlet.eal ... success
comparing data obtained from EaxMan.dll with values retrieved manuall from eal file ... success
datasets are identical
Statistics: Geometry Sets: 1 [ "Geometry1" ]
Material Sets: 1 [ "Walls" ]
Environment Sets: 9 [ "Tunnel" "Tunnel mouth" "Large chamber" "Small chamber" "Big room" "Underground" "Small room" "Tiny room" "In the lava aargh!" ]
Sources Sets: 1 [ "stone04.wav" ]
Sources placed: 0
ReadEalFiles.exe -d <filename>.eal
does read the .eal file with EaxMan.dll and then asks for user input. The coordinates X, Y and Z need to be entered by the user as floating point numbers in the range -32767.0 ... +32767.0.
The dynamic analysis uses the API call GetListenerDynamicAttributes which takes the entered coordinate and gives back the ID of the environment preset that applies to a Listener standing at that coordinates.
According to the documentation, EAXManager uses the same coordinate system as DirectSound with positive values of X, Y and Z pointing forward, right and up. It is unknown to the author if Unreal Tournament uses the same orientation, although the v469 patch programmers can easily check the source code.
GetListenerDynamicAttributes seems to be a major piece of what EAXManager does. It calculates internally which zone the coordinates lie in and then returns the ID of the environment that the Audio renderer should use.
An example out looks like this:
> ReadEalFiles.exe -d CTF-Gauntlet.eal
analyzing file manually: CTF-Gauntlet.eal ... success
analyzing file with EaxMan.dll: CTF-Gauntlet.eal ... success
Enter coordinate x: -1100.35
Enter coordinate y: 0
Enter coordinate z: 0
query result: Environment ID: 0 - EAX Environment: EAX_ENVIRONMENT_HALLWAY
Enter coordinate x: 0.6
Enter coordinate y: 0.333
Enter coordinate z: -1.2
query result: Environment ID: 2 - EAX Environment: EAX_ENVIRONMENT_AUDITORIUM
Enter coordinate x: 1400
Enter coordinate y: -80
Enter coordinate z: 160
query result: Environment ID: 3 - EAX Environment: EAX_ENVIRONMENT_AUDITORIUM
What EaxMan.dll actually does is reading all EAX parameters from the .eal file and returns them. It does not set up DirectSound3D / EAX calls but only provides the parameters which the program then needs to use.
This means that an implementation in Windows should be doable without major problems. EaxMan.dll can be used to gather all parameters needed. OpenAL Soft can wrap the EAX parameters into EFX calls. So the sound effects can be enjoyed on newer versions of Unreal Tournament as well.
EaxMan.dll is a library that can be run on Windows. But Unreal Tournament can be run under other operating systems. So there is a desire to make the EAX sound effects available under different OS' as well.
OpenAL Soft does work on all OS that Unreal Tournament v469 does run on. So wrapping the EAX parameters in EFX calls is possible on every OS and the sound effects can be enjoyed on any system.
The ReadEalFiles tool provided in this repository does implement a way to read all the data from the .eal files. And it compares it to the results obtained with EAXManager to verify the results. The output of that tool when analysing CTF-Gauntlet.eal looks like this:
> ReadEalFiles.exe CTF-Gauntlet.eal
analyzing file manually: CTF-Gauntlet.eal ... success
analyzing file with EaxMan.dll: CTF-Gauntlet.eal ... success
comparing data obtained from EaxMan.dll with values retrieved manuall from eal file ... success
datasets are identical
The data retrieved with both methods (using EAXManager API calls and the custom RIFF analysis done in ReadEalFiles) is compared. For all but one .eal files the data retrived with both methods are idendical. The only exception is DM-Grinder.eal
ReadEalFiles shows a mismatch on DM-Grinder.eal. This .eal file has 8 environments defined. The environment with ID 3 is named Small verb. The environment with ID 5 is named Small Verb. The only difference therefore is the lowercase/uppercase V. Both environments have different values as parameters, so they are treated as two separate presets when analyzed manually. Using EaxMan.dll, the API call GetEnvironmentID(name) is used, which gives the same ID 3 with both names. So it does not distinguish between upper and lower case letters and gives back the first ID it finds.
The correct behavior cannot be determined until the geometry data is decoded and we can see what exactly is stored there. The fact that the dynamic API call GetSourceDynamicAttributes does return an ID and not the environment name is a strong hint that the manual analysis is correct.
Therefore, this reported mismatch must be considered a bug in the EaxMan.dll API call GetEnvironmentID(name).
Still there is one problem left: EaxMan.dll does do the 3D position calculations internally as described in the ReadEalFiles dynamic analysis section. The source code for EAXManager was not disclosed and the calculations are only described in a rudimentary way. So there is yet decoding work to do.
GetListenerDynamicAttributes only gives back the environment ID and ReadEalFiles shows that there is way to get the according parameters without using EaxMan.dll. A straightforward approach is to read out all data and save it outside the .eal files. The most basic approach would be to loop over all coordinates and store the results of GetListenerDynamicAttributes.
The downside to this approach is the resulting file size. If we want to have a resolution of 1 cube unit, we would iterate all three coordinates in the range -32767 to 32767. So a total of 65536³ = (2^16)³ = 2^48 values needed to be stored. If the ID is stored in 1 Byte, that would result in a file size of 256 TB.
Even if we only consider the playable space this does not improve too much. For example, the player reachable space in CTF-Face is X:-6238 to +10712, Y:-8539 to 8361, Z:-7358 to 10642. This still results in a file size of 4.7 TB.
There is still room for improvement. Compressing the data, storing more IDs per byte, further reducing the volume are some ideas. Still, the brute force method does not seem promising except as a last resort.
So a refined approach is needed. An idea is to not store the ID for each coordinate, but look at the coordinates where the ID changes and calculate back the shape of each environment zone from there.
This is a task for someone with experience with 3D coordinate calculations.
A better approach would of course be:
The geometry data is stored inside the .eal file and the calculation is done by the EaxMan.dll. So maybe there is a way to reverse engineer the actual calculation and then use that.
Replacing EaxMan.dll with custom code for OS other than Windows is possible but a feasible approach to obtain the 3D geometry data has still to be identified.
PlayEalFiles is a simple proof of concept for that conclusion. Just like ReadEalFiles, PlayEalFiles is a quick'n'dirty tool with the sole purpose of testing the EFX calls on OS other than Windows.
It uses the algorithm from ReadEalFiles to read the environment presets from a .eal file. It then creates a EFX call with the parameters from the preset and uses OpenAL Soft to play the sounds with the effects.
The tool shows that those effects can be read and applied on amd64 Linux without any need for EaxMan.dll or the modifiex Galaxy.dll.
./PlayEalFiles <sounds>.wav <level>.eal
The wave file is played once without any effects applied as a reference.
It then loops over all environment effects described in the .eal file and plays the wave file once with each effect applied.
Compile with any C++20 compatible compiler.
Dependencies: libsndfile and libopenal
The source code for PlayEalFiles contains pieces from openal-soft, which is licensed unter LGPL. Those pieces remain under the LGPL.
Other pieces are heavily derived from the RIFF decoder and information RIFF File Structure
Another helpful resource in making PlayEalFiles work was the OpenAL short example
And a final shoutout and thanks to Createive for those EAX patches, the folks behind websound.ru who saved those old SDKs. And to the contributors of OldUnreal/UnrealTournamentPatches who keep this game alive.