The XL Engine is and will remain free, donations are purely optional but greatly appreciated.

Example function in assembly code, responsible for drawing and updating the Main Menu and Province Selection screen when creating a new character.
This code is now fully functional.

Assembly, derived from debugging the executable with inline decompilation done by hand. Note that these were rough notes to help with the real decompilation process later. The resulting C code is shown after the assembly.

** func_19CF7D (Menu)
locals (crappy named version):
byte i;             //[ebp-0014] --dword spacing?
dword j;            //[ebp-0018]
dword k;            //[ebp-001C]
dword l;            //[ebp-0020]
dword m;            //[ebp-0024]

[19CF7D] ;Function Start.
push ebp                        ;Function header, push used registers, setup ebp and esp.
mov ebp,esp
push ebx
push ecx
push esi
push edi
sub esp,00000014
mov [ebp-0024],eax              ;m=eax //=0027D394 MainMenu, =00000000 Province Select
mov [ebp-001C],edx              ;k=edx //=00000003 MainMenu, =00000000 Province Select
cmp byte [00245EA0],00          ;if ( [245EA0] == 0 ) goto MAIN_LOOP_START
je 0019CFA0
call 002463D6                   ;func_2463D6()
jmp 0019CF90                    ;goto 19CF90
cmp byte [00245EA0],00          ; @mem: 00 while looping.
jnz 0019D065                    ;if ([00245EA0] != 0) return 0x00FFFFFF
call 002463D6 ;func_2463D6()
movsx edx,[00245EA6]            ;edx = [245EA6] @mem: 0007
movsx eax,[00245EA4]            ;eax = [245EA4] @mem: 0064
call 00246573                   ;func_246573()
call 0025DA48                   ;i = func_25DA48()
mov [ebp-0014],al               ; return value, store in local variable i
cmp byte [ebp-0014],00          ;if ( i == 0 ) goto 19D03F
jz 0019D03F
test byte [002A08F4],01         ;if ( !([2A08F4]&0x01) ) goto 19CFEA
je 0019CFEA
xor eax,eax
mov al,[ebp-0014]
cmp eax,001B                    ;if ( i == 0x1B ) goto 0019CFEC
je 0019CFEC
jmp 0019CFF8 ;goto 19CFF8
mov dword [ebp-0020],FFFFFF      ;l = 0x00FFFFFF
jmp 0019D06C                     ;goto 19D06C
cmp dword [ebp-001C],0000        ;if ( k == 0 ) goto 19D03F
je 0019D03F
xor eax,eax
mov al,[ebp-0014]                ;eax = i //this is the return value from func_25DA48() above.
call 001C0E84                    ;i = func_1C0E84() //<- this uses i as an input through eax.
mov [ebp-0014],al
mov dword [ebp-0018],000000      ;j = 0;
mov eax,[ebp-0018]
cmp ax,[ebp-001C]                ;if ( j < k ) goto 19D026
jl 0019D026
jmp 0019D03F                     ;else goto 19D03F
mov eax,[ebp-0018]               ;eax = j
inc dword [ebp-0018]             ;j++
jmp 0019D013                     ;goto 19D013
movsx edx,[ebp-0018]
add edx,[ebp-0024]
mov al,[ebp-0014]
cmp al,[edx]                     ;if ( i != j + m ) goto 19D03D
jne 0019D03D
movsx eax,[ebp-0018]
mov [ebp-0020],eax
jmp 0019D06C                     ;else return j
jmp 0019D01E                     ;goto 19D01E;
push 0004                        ;function parameters = (000000EA, 0004)
push 000000EA
mov ecx,00283230                 ;@mem: "support.c.pick00i0.img"?
mov ebx,0000FA00                 ;frame buffer size 8bpp@320x200=64000 bytes.
mov edx,[0025E7F0]               ;@mem: frame buffer address (002F0DC0)
mov eax,000A0000                 ;video memory address
call 001C1033                    ;handles framebuffer blitting to video memory.
jmp 0019CFA0                     ;goto MAIN_LOOP_START
mov dword [ebp-0020],FFFFFF      ;l = 0x00FFFFFF
mov eax,[ebp-0020]               ;return l
lea esp,[ebp-0010]               ;function return block:
pop edi                          ;setup esp to return location,
pop esi                          ;restore used registed to their original values, etc.
pop ecx
pop ebx
pop ebp

And the resulting C code after messaging the code into using C constructs, 
which I did by hand (as you might guess):

Note variable definitions are omitted for space. Also note that parameters passed to 
functions do not all use the stack, parameters passed using registers are 
also shown as regular inputs for clarity (and to make the C code work of course).

//This function draws the Main Menu or Province Select (when creating a character)
//It also handles user input and returns when the player has selected an option.
//Returns the option selected or 0x00FFFFFF.
//optionList memory address stored in eax, optionCount in edx
int Menu(byte *optionList, dword optionCount)
    while (curMouseBtnStatus != 0)
        //Poll the mouse state (position and button status).
    //main loop
    while (1)
        if ( curMouseBtnStatus != 0 )
            return 0x00FFFFFE;

        //Poll the mouse state (position and button status).
        //parameters passed using eax, edx
        DrawMousePointer(mouseX, mouseY);
        //get the keyboard input.
        word retval = GetInput();
        byte ascii  = retval&0xff;

        //is there any input?
        if ( ascii != 0 )
            if ( menuFlags&0x01 )	//2A08F4 must control whether you can go "back" or not.
                if ( ascii == keyCode_Esc )

            if ( optionCount > 0 )
                //retval passed as eax.
                byte key = RemapKeyCode(ascii);

                //are any of the options in optionList[] selected?
                for (dword i=0; i<optionCount; i++)
                    if ( key == optionList[i] )
                        return i;	//option 'i' selected.

        //first two parameters passed using the stack, next 4 using registers: ecx,ebx,edx,eax
        BlitFrameBuffer(0x000000EA, 0x0004, 0x283230, videoMemSize, frameBuffer, videoMemAddr);

    //no option selected.
    return 0x00FFFFFF;

4 Responses to “Decompile Example”

  • Name:

    I have a question for you. What tool did you use to disassemble the executable? Plain old Turbo debugger?

    Kudos for all that hard work.

  • Stephen Collings:

    Agreed, I want to know what tools you used to do that! I’ve always wanted to pick apart a couple old DOS executables, including DARK.EXE…

  • luciusDXL:

    I just use the DosBox debugger and Visual Studio.
    Some basic steps:
    1. Identify loops and program flow using the debugger.
    2. Extract/copy assembly code for the appropriate functions, annotating as needed (jump addresses, etc.) using the debugger.
    3. Figure out what the code is actually doing and annotate the assembly code. Usually this just involves translating sequences with their higher level C equivalent but sometimes takes looking at memory or stepping through code to figure out.
    4. Convert annotated assembly code into functions, give variables and functions proper names based on functionality.
    5. Once a loop is complete, go on to the next one. Sometimes this means going “up” a level and sometimes to the next program state.

    Oh and DARK.EXE is next on my list to finish off DarkXL. :)

  • Have you considered doing an X-WingXL or TIEXL?

Leave a Reply

The XL Engine is and will remain free, donations are purely optional but greatly appreciated.