Static linking of Bassmod in Delphi
BASS and BASSMOD are very well known freeware libraries for playing XM, IT, WAV and many more sound file formats. They are widely used in keygens and other apps. However, authors only distribute them in a DLL form, there is no LIB file or any other option for linking them statically.
Last week someone resurrected an old thread at Tuts4You and asked how to convert DLL to LIB and link it statically with Delphi. I gave poster the standard answer but he was still running in all sorts of issues. So, can it be done?
Then answer is - yes. But it's not easy.
Note - This article is written for good old Delphi 6/7/2007. Since Delphi XE2 the process should be easier as the linker was improved to use COFF OBJ files directly. However, I don't have those new Delphi versions, so I can't test the claims.
Steps to be taken
I'll make a list of all necessary steps first and then I'll discuss them in details.
- Unpack the DLL properly
- Convert DLL to LIB
- Extract OBJ files from LIB
- Convert COFF OBJ files to OMF OBJ files
- Make those OMF files usable by Delphi
- Write a wrapper unit that works around Delphi limitations
Unpack the DLL properly
First step sounds easy, right? It isn't.
The LIB provided in Tuts4you thread is badly unpacked. Sure, it can work when compiled with MASM. It can be made work with Delphi, but you'll need to hex-edit compiled EXE file first. The reason for this is extremely primitive Delphi compiler/linker. You have no control over PE section names or attributes. It relies on specific section names and always makes code section read-only. But the LIB from tuts4you uses one segment for both code and data and it must have read-write-execute characteristics. Ooops.
So, we need to unpack DLL ourselves using all the standard steps. PE packer is a very simple one, so you can easily find OEP, dump the file, load DLL at different imagebase, find the OEP and make a 2nd dump, use 2 dumps to fix the relocations using Relox and finally restore Import Table using Scylla or ImpRec. Nothing new here.
Once you've unpacked the DLL, you will have to detect section boundaries and create new PE section table. When you're at OEP of bass.dll, check the memory map in some process exploring tool. You'll see the sections and their characteristics nicely:
Now use any PE editor to create appropriate PE section headers:
To make Delphi happy, code section should be named _TEXT and data section should be named _DATA. All the sections you don't need in final OBJ file, should be named ".reloc", ".edata" or similar - Dll2lib will remove them automatically.
Convert DLL to LIB
Well, this step is easy. Use DLL2LIB (google "DLL.To.Lib.v1.42.Full.Retail-DLL2Lib" or get trial version from official site), leave all the default settings and press "Start convert".
Extract OBJ files from LIB
For next few steps you'll need objconv.exe by Agner Fog. It's better to download the latest version, as earlier versions didn't support extracting LIB files.
It's a simple command objconv.exe -lx bass.lib
Convert COFF OBJ files to OMF OBJ files
That's also simple. Just run objconv.exe -fomf bass.obj bass-omf.obj
Make those OMF files usable by Delphi
Delphi imposes quite a few limitations to OBJ file format. Some of them are documented, some of them aren't. So, it's better to rely on special tools made for this purpose, like omf2d.exe by EliCZ.
I'm sure that objconv.exe can do the same, but I'm too lazy to try to figure the right command line parameters. So, just run omd2f.exe bassmod-omf.obj bassmod-omf-d.obj
Note - omd2d.exe will mess up some decorated names from msvcrt.dll, like "??2@YAPAXI@Z". That's not a problem, we'll fix that in the wrapper unit.
Write a wrapper unit that works around Delphi limitations
This is also tough. And again the problems are caused by the primitive Delphi compiler/linker.
Delphi doesn't support direct API calls, all API calls will go through the thunk table. When you try to reference any external API from Delphi code, in reality you'll get address of the thunk code.
For the same reason in Delphi you can't access exported global variables from another DLL.
Unfortunately BASS/BASSMOD uses both direct API calls and global variables from msvcrt.dll. Little bit of clever hacking is required to work around that - you'll have to load msvcrt and other DLLs from unit initialization code and use GetProcAddress to get the required addresses.
So, the implementation part of the unit will look like this:
hKernel32 : dword;
AreFileApisANSI : pointer;
CloseHandle : pointer;
hKernel32 := Windows.LoadLibraryA('kernel32.dll');
AreFileApisANSI := Windows.GetProcAddress(hKernel32, 'AreFileApisANSI');
if AreFileApisANSI = nil then goto fail;
CloseHandle := Windows.GetProcAddress(hKernel32, 'CloseHandle');
if CloseHandle = nil then goto fail;
In addition to that we need to call the original DllMain function to make sure that BASS is initialized properly:
function Bass_DllMain(hinstDLL : dword; fdwReason : dword; lpvReserved : dword) : dword; stdcall; external;
Bass_DllMain(Windows.GetModuleHandle(nil), DLL_PROCESS_ATTACH, 0);
Bass_DllMain(Windows.GetModuleHandle(nil), DLL_THREAD_ATTACH, 0);
As a final touch, in the finalization part of unit we'll have to call DllMain again to make sure all resources are freed properly.
Putting it all together
I already outlined all the steps needed. Anyone with proper skills should be able to replicate them and make his/her own BASS unit.
For those who are lazy - here is the package with Delphi units+obj files + all the intermediate files + compiled projects from BASS/BASSMOD examples to show that it really works.
Unpacking DLLs #1: Tutorial by Mr. eXodia
Unpacking DLLs #2: How to use Relox in few simple steps