19 May

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.

  1. Unpack the DLL properly
  2. Convert DLL to LIB
  3. Extract OBJ files from LIB
  4. Convert COFF OBJ files to OMF OBJ files
  5. Make those OMF files usable by Delphi
  6. 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:
BASS memory map in PETools

Now use any PE editor to create appropriate PE section headers:
Section headers in CFF
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:

In addition to that we need to call the original DllMain function to make sure that BASS is initialized properly:

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.

Have fun!

Useful links

Unpacking DLLs #1: Tutorial by Mr. eXodia
Unpacking DLLs #2: How to use Relox in few simple steps
Omf2d: https://www.mediafire.com/?hsksyjwnwlaw3zb

28 thoughts on “Static linking of Bassmod in Delphi

  1. I really like this kind of stuff.
    I'm sure it required a lot of research, test and work ... so a big THANK YOU for sharing your knoledge.

    Best Regards,

  2. Thank you all for kind words. :) This is the kind of project I really like - not too easy but also not too time consuming.

    Yes, it required some research. I had to disassemble & debug dcc32.exe to find out what's causing "invalid object file" errors. I also had to create small OMF file parser to analyze individual OBJ data entries - IDA shows just disassembly and hides all the details from user.

  3. Thanks for your great work
    by the way do you mind shaing your OMF file parser just in case someone need to investigate future lib ?

    • @sinan: It's a very simple command-line tool made for a specific purpose. The output from my tool is not useful, if you're not reading OMF specification at the same time. ;)


      But I will consider making a proper template for 010Editor some day. ;) That way you'll be able to see the OMF contents in nice UI, edit data and see changes immediately.

    • @mustafa: If you could upload those OBJ files somewhere, I will take a look and find out why it doesn't work for you. Most likely you didn't convert it properly and encountered some of Delphi compiler limitations.

    • Your OBJ file is corrupted. I don't know how it happened (probably you downloaded or copied it from somewhere).
      This is how it looks in hex editor: http://i.imgur.com/89h05Jb.png - see all those "20"? There should be "00" instead.

      Get a good OBJ file and try again. If it still doesn't work, send me a link again. :)

        • Your OMF file has segment named ".text$mn". If you read my article carefully, you'll see that Delphi needs code segment called "CODE".

          So, you should run:

          to rename the segment.
          Then run

          to make it usable with Delphi.

          Problem solved! :)

          I will make a longer post about these problems next week.

    • I cannot debug screenshots. ;) Could you please upload the entire project (compiled EXE+Delphi source+OBJ file you are using) - and I will check it.

      I see 'external' declaration of RtlUnwind in your screenshot. So, my first guess is that you have problems with imported APIs. So, I suggest that you re-read my article and see the source code on how the imported APIs should be done.

    • I loaded your project in OllyDbg and was able to find the problem. It really is a problem with imported APIs.

      See here:

      this jumps to address 523825FF, which is wrong. And this is what should happen instead:

      It should call TlsAlloc. :)

      So, read again the chapter "Write a wrapper unit that works around Delphi limitations", study my sources and make sure you implement all imports in the way Delphi needs them.

  4. Now everything is apparent.
    the problem was a means of functions addresses.
    thanks again.
    i'll be back any problem.

    • @Sn!per X: nice job, it saves quite a lot of manual work. :)

      One small issue - Delphi DLLs often import the same API twice. In that case the unit you generate will fail to compile with error message like this:
      DLLWrapper_wrapper.pas(108) Error: E2004 Identifier redeclared: 'GetModuleHandleA'
      DLLWrapper_wrapper.pas(110) Error: E2004 Identifier redeclared: 'Sleep'

  5. Hello kao,
    thanks for the quick reply, i will fix the issue you mentioned previously
    also i am planing to add more functions to the tool if to make the work easier.
    btw, i am facing a weired obj file:

    i followed every step in ur tut and finally the obj wont compile, getting bad object.... inspecting for the problem lead me to:

    "All the sections you don’t need in final OBJ file, should be named “.reloc”, “.edata” or similar – Dll2lib will remove them automatically".

    opening that obj with HexWorkshop shows in the second line that
    there is a section named reloc did not deleted by Dll2lib.

    any explanation for that?
    Original DLL:

    - last question what if i have c LIB the question is: is it possible or is there a way to rename its sections ('_TEXT, _DATA') before i processed with converting it?

    thanks in advance.

    • I looked at your DLL. I'm not sure how you generated your OBJ file, but it's definitely wrong.

      #1 - when I convert DLL to LIB using Dll2Lib 1.42 Full with all default settings, ".reloc" segment is deleted from OBJ file. ".rdata" and other useful segments are present in OBJ file.
      #2 - your DLL has a special ".shr" segment. You should keep it. It's a read/write data segment, so you should rename it properly as well.

      Once you have all your segments named properly, Delphi will happily link your OBJ file.

Leave a Reply

Your email address will not be published.