Half-Life Programming - Debugging Last edited 6 months ago2023-10-27 15:27:39 UTC

Half-Life Programming

Programming is hard, and programming with the Half-Life SDK is harder. Sooner or later, something will go wrong, and you'll have no idea why. What can you do about that? The answer is, of course, debugging!

Getting Half-Life to debug properly takes a little bit to set-up, so this article will guide you through what you need to do to get everything working.
This isn't a tutorial on how to use Visual Studio or GDB - only how to set up the Half-Life SDK for those tools. For information on how to use the debuggers, take a look here:

Debugging on Windows using Visual Studio

This method is recommended for most users, as VS makes debugging very convenient and easy. Assuming you have VS already set up and your mod is running, there are two extra things you need to do to get debugging working properly.

Copy DLL files on build

Note: If you're using Half-Life Updated then this has already been done for you. All you need to do is edit filecopy.bat to point to your mod directory and you're all set!
In Visual Studio, right-click on your hldll project and select Properties. Expand Build Events and select Post-Build Event. The easiest way to deal with this is to get the post-build event to run a custom batch file on completion. If you're using the halflife-updated repository, enter this script into the Command Line option (drop down the option and select <Edit...> for a multi-line editor):
cd /D "$(ProjectDir)..\.."
cmd /c copy-server.bat
User posted image
Do the same for the hl_cldll project, except change the batch file to copy-client:
cd /D "$(ProjectDir)..\.."
cmd /c copy-client.bat
These commands will simply run the corresponding batch files in the root folder of your repository. Next, we'll create those files.

Go to the root folder of your repository and create the copy-server.bat file. In this example, that file will be located at D:\Github\halflife-updated\copy-server.bat. The contents of that file should be a command to copy the hl.dll file to your mod's location. Here's an example, but you must modify it to match the location of your mod, or it won't work properly!
robocopy ".\projects\vs2019\Debug\hldll" "C:\Program Files (x86)\Steam\SteamApps\common\Half-Life\testmod\dlls" hl.dll /njh /njs /ndl /nc /ns /np
Do the same thing for the copy-client.bat file. Here's an example of the contents:
robocopy ".\projects\vs2019\Debug\hl_cdll" "C:\Program Files (x86)\Steam\SteamApps\common\Half-Life\testmod\cl_dlls" client.dll /njh /njs /ndl /nc /ns /np
If your project has already been compiled, you should run these batch files now to make sure they copy the files into the right location. Take note that the Debug folder is hard-coded in these files. For the most part, that's fine - but when you release the final version of your mod, you should consider doing a Release build, which will put the compiled DLLs into a different spot. You cannot debug a Release build, so only change the build configuration once you're satisfied that everything is working.
Remember: You must update the batch files to match your mod's actual location! Do not just copy this text without modifying it.

Set a startup project

First, choose a project to use as your "startup project". It doesn't matter which one you choose - so I'll assume you'll be using the hldll project. Right click on that project, and choose Set as Startup Project. Next, we want to make sure that both your projects are built when you debug the project, so right-click the project again and choose Build Dependencies > Project Dependencies.... Here, check the box next to hl_cldll and click OK. Doing this will ensure that when you click the green "Debug" button, both projects will be compiled and copied into your mod directory.
User posted image

Set up the debugger commands

Right-click your startup project again and choose Properties. Select the Debugging section.

The important options here are Command and Command Arguments, and you should also set Working Directory as well. Here's what you should set them to: Here's what it should look like:
User posted image

Run the project

You should be all done now - click the green "play" button in VS, or press F5 to run the project. Assuming everything is set up properly, the code will be compiled, the post-build event will copy the DLLs into your mod, and then VS will launch Half-Life and start debugging it. Try placing a breakpoint on void CWorld :: Spawn( void ) or void CHud :: Init( void ) to make sure debugging is working on both the server and client projects respectively.

Debugging on Linux using GDB

Note: in the examples below, replace <username> with your Linux username.

If you like, you can use a shell script or update the makefile in order to copy the client and server .so files to your mod directory. Though, this isn't mandatory. Since, on Linux, the compiler and the debugger processes are entirely separated. This guide assumes that you've already compiled your project and copied the .so files into the correct places.

Setting up a terminal

Open a terminal and change the directory to the Half-Life game installation directory. This is typically located under your user directory with this path: /home/<username>/.steam/steam/steamapps/common/Half-Life. If you want to browse to this directory using a Files window or similar interface you may need to enable the Show hidden files option first to see the .steam directory.

Setting the library paths

Open a terminal and enter the following:
export LD_LIBRARY_PATH=/home/<username>/.local/share/Steam/ubuntu12_32/steam-runtime/pinned_libs_32:/home/<username>/.local/share/Steam/ubuntu12_32/steam-runtime/pinned_libs_64:/usr/lib/x86_64-linux-gnu/libfakeroot:/lib/i386-linux-gnu:/usr/local/lib:/lib/x86_64-linux-gnu:/lib32:/libx32:/lib:/home/<username>/.local/share/Steam/ubuntu12_32/steam-runtime/lib/i386-linux-gnu:/home/<username>/.local/share/Steam/ubuntu12_32/steam-runtime/usr/lib/i386-linux-gnu:/home/<username>/.local/share/Steam/ubuntu12_32/steam-runtime/lib/x86_64-linux-gnu:/home/<username>/.local/share/Steam/ubuntu12_32/steam-runtime/usr/lib/x86_64-linux-gnu:/home/<username>/.local/share/Steam/ubuntu12_32/steam-runtime/lib:/home/<username>/.local/share/Steam/ubuntu12_32/steam-runtime/usr/lib:/home/<username>/.local/share/Steam/steamapps/common/Half-Life
Replace <username> with your Linux username.

This will set the library paths to search to the directory containing the game executable and the Steam runtime directories containing libraries used by Half-Life 1.

The paths used by Steam may change with updates to the Steam runtime. If a problem occurs, you can retrieve the list of paths used by adding this command to the game's launch options in Steam:
env > ~/envdump #%command%

This will create a text file in your home directory called envdump containing all environment variables used to launch the game. LD_LIBRARY_PATH is among them and can be used to set the correct paths.

Make sure to remove the command after using it to ensure the game still launches correctly.

Launching the game

Now enter the following commands in your terminal:

file hl_linux
set args -dev -console -w 1280 -h 720 -game testmod
See the Windows/VS section for an explanation of the launch arguments - you can change them as needed. This will launch Half-Life with the specified mod - be sure to change it to the actual name of your mod folder.

Debugging "could not load library client" errors

Sometimes when developing a mod you will encounter "could not load library client" errors during startup, preventing you from testing or debugging your mod at all.

When this happens it typically means your mod's code references dependencies that can not be found, such as missing libraries or undefined variables and functions. The Half-Life engine does not report the error string for this kind of error, so it is difficult to diagnose.

To help find out what's happening, follow these steps:

On Windows

1. Download this tool: https://github.com/twhl-community/NativeDllTester/releases. This is a small tool to get the error code returned by LoadLibrary when it fails to load a library.

2. Place the executable in the Half-Life directory. This ensures that it will resolve paths the same way that the engine does.

3. Launch the tool. Enter the path to the dll or click Browse to select it, then click Load.

4. If an error occurs, the Error Code field contains the native error code. The translated error message is displayed in the field below that.

5. See the List of Error Codes to find the corresponding name and description in English. This should help you to pinpoint the cause of the problem that's causing it to fail to load.

On Linux

1. Start by setting up GDB by following the instructions above. If you try to launch your mod in GDB, you will notice that it fails to load there as well.

2. To figure out what's causing the problem, instead of running your mod, enter these commands after the file hl_linux command:
set args -dev -console -w 1280 -h 720 -game valve
Note the -game valve argument - this will launch Half-Life itself, using the unmodified default binaries. We need this because we're going to need an active process instance to execute some code through GDB.

3. Switch to the terminal with your GDB instance and press the button combination CTRL+C. This will interrupt execution and allow us to execute code in GDB directly. Now enter these commands (be sure to replace <mod directory name> with the name of your mod directory):
call (void*) dlopen("<mod directory name>/cl_dlls/client.so", 2)
call (char*) dlerror()
The first command calls the function dlopen with the path to the mod client library. The second parameter is the constant RTLD_NOW which tells dlopen to load the library and resolve all undefined symbols. By using this parameter any symbols that cannot be found in any other libraries will cause the library to fail to load, and the missing symbol will be returned as part of the error string.

The second command calls the dlerror function. This function returns the error that occurred during the last dlopen call if any errors occurred. If your library fails to load, this will be a string explaining what went wrong.

For example, if a global variable is declared but not defined you will get an error string that looks like this:
$2 = 0x8ddf5b0 "<mod directory name>/cl_dlls/client.so: undefined symbol: <variable name>"
With this information, you should be able to track down and fix the cause of the problem.
Note: the (void*) and (char*) used before the function name are needed because GDB needs to know the return type of the functions in order to print the results. These are C-style casts. If you omit them GDB will not be able to execute the functions and will report an error instead.
For more information about the dlopen and dlerror functions, consult the documentation.

1 Comment

Commented 11 months ago2023-06-17 09:22:21 UTC Comment #105355
The symbolic link is right.

Your path is no longer correct because Valve seems to have changed it recently.

You must log in to post a comment. You can login or register a new account.