The missing sources part 2

Now I have a more clear idea how to continue the work, let’s document the next steps.

First of all I want to remember that the first test of building the application listed about 30 missing functions, all used only in the first source file but I’m sure others are used in other source files. The missing functions may be part of the Funcky library or an application level library of which I only have the .lib file. A friend decompiled a couple of executables so that I could recover some of these missing sources. A blinker generated file I found in the archive lists all the object files linked in one exectuable and the functions it contains so that I could create the source code files using the same name.

I used the online Norton Guide of the Funcky libs to detect which functions were used from the library, and from the first list of missing functions I had the following list:

CLS: clear entire screen
ROLOC: reverse attribute
PRINT: print a string
BORDER: set border color (CGA/EGA/VGA)
CLOCK24, UNCLOCK24: display/turn off a 24 hour military format clock
ONDO: execute expression  in the list
FLRESET: reset default font/mode
CENTER: add leading spaces to center in  width
AMAXSTRLEN: get longest string length in array
CURDRIVE: get the logged drive letter (d:) 
ALEN: length of array without undefined elements
CHDIR: change directory

Looking at the samples in the NG and their use in the code I created the following code:

#include "set.ch"


// Unused, no more valid, functions

// CLOCK24, UNCLOCK24: display/turn off a 24 hour military format clock

FUNCTION clock24()
   RETURN .T.

FUNCTION unclock24()
   RETURN .T.

// BORDER: set border color (CGA/EGA/VGA)

FUNCTION border( n )
   RETURN .T.

// FLRESET: reset default font/mode

FUNCTION FLReset()
   RETURN .T.


// STUB functions (need changes)

// CURDRIVE: get the logged drive letter (d:)
// PROBLEM: returns a fixed drive letter
FUNCTION curdrive()
   RETURN "f:"

// CHDIR: change directory
FUNCTION chdir( cPath )
   hb_cwd( cPath )
   RETURN .T.

// CENTER: add leading spaces to center in <nn> width
// PROBLEM: samples in NG says that there should be a trim()
FUNCTION center( c, n )
   RETURN PadC( c, n )

// ROLOC: reverse attribute
// PROBLEM: Returns a fixed value
FUNCTION roloc( x )
   RETURN "w"

// PRINT: print a string
// PROBLEM: nColore not used
FUNCTION print( y, x, cTesto, nColore, nRepl )
   IF ! HB_ISNUMERIC( nRepl )
      nRepl := 1
   ENDIF
   @ y, x SAY Replicate( cTesto, nRepl )
   RETURN .T.

// CLS: clear entire screen
// PROBLEM: cChar not used, not all cases covered
FUNCTION cls( cColor, cChar )
   @ 0, 0 CLEAR TO MaxRow(), MaxCol()
   RETURN .T.

// Completed functions, may need changes

// ONDO: execute expression <n> in the list
FUNCTION ondo( nValue, ... )
   LOCAL b, c
   IF ! HB_ISNUMERIC( nValue )
      ? "ERRORE ONDO valore non numerico"
      QUIT
   ENDIF
   IF nValue > Len( hb_AParams() ) - 1
      ? "ERRORE ONDO, passato valore", nValue, "ma solo ", Len( hb_AParams ) -1, "valori"
      QUIT
   ENDIF
   c := hb_AParams()[ nValue + 1 ]
   b := hb_macroBlock( c )
   Eval( b )
   RETURN .F.

// AMAXSTRLEN: get longest string length in array
FUNCTION aMaxStrLen( a )
   LOCAL n
   LOCAL i

   IF Len( a ) == 0
      RETURN 0
   ENDIF
   n := Len( a[ 1 ] )
   FOR i := Len( a ) TO 2 STEP -1
      IF HB_ISCHAR( a[ i ] ) .AND. Len( a[ i ] ) > n
         n := Len( a[ i ] )
      ENDIF
   NEXT
   RETURN n

// ALEN: length of array without undefined elements
FUNCTION aLen( a )
   LOCAL i
   LOCAL n := 0

   FOR i := 1 TO Len( a )
      IF ValType( a[ i ] ) != "U"
         n++
      ENDIF
   NEXT
   RETURN n

Then I created funcky.hbp file

# I want a library
-hblib
# I want to call it "funcky"
-ofuncky
# 
src\funcky
</pre>

And built it
<pre>
hbmk2 funcky.hbp
</pre>

The library was successfully created.

I then updated exe01.hbp file adding the following lines:
<pre>
-L.
-lfuncky
</pre>
to link the library just created. I could have used a .hbc file. I will, in the future.

Now the build process reports only the functions that should be in the missing application-level library. I create a .hbp file for this new library (I called it applib) adding all the decompiled functions that I'm aware of, using the blinker generated file as a guide.

1
# I want a library
-hblib
# I want to call it "applib"
-oapplib
# source files
src\decomp01
src\decomp02
..
src\decomp13

There are still missing functions reported by the linker, and all used in the first source file only. Looking at the names and how they are used in the code, they are almost all related to screen handling, to create fancy effects, like imploding or exploding windows... Just one is another clone of padr function.
So I create all empty stub functions, except for the clone function, and finally added a function I usually use for debugging:

#pragma BEGINDUMP
#include <windows.h>
HB_FUNC( OUTPUTDEBUGSTRING )
{
    OutputDebugString( (LPCSTR) hb_parcx( 1 ) ) ; 
}
#pragma ENDDUMP

Now the last step, trying to build the program and finally get the new missing functions from the next source files.... ready ?

hbmk2 exe01.hbp

Nice sensation when you get the errors you were looking for !!!
The errors had the same pattern: if there is only one missing function the linker processes the following file, otherwise it stops. So I have an error for file02.prg and file03.prg but 3 for file04.prg so I have to solve them before getting new missing functions...

Now I have to loop: compile and get the list of the missing functions. If the function is from Funcky lib, check its docs and its usage, then create a clone.
If not from Funcky lib, check in the decompiled files. At the moment I had just 2 minor executables decompiled; the main one, with almost all functions, fails to decompile. I know this will be a problem in the future... I know...

The job was quite repetitive, but in the process I found something interesting...

I had to correct this code snippet that failed to compile:

   Chr( K_CTRL_HOME ) + Chr( K_DOWN )  // fails to compile in Harbour

there was obviously a missing KEYBOARD()... I don't have Clipper 87 installed so I can't check if it also failed to compile... but anyway, it never worked as intended...

I found several Funcky functions used to set/delete the volume label, or check if a drive is ready (A:) or if the passed string points to a valid dir. I created stubs to always return .T. These functions are used just in a couple of source files to perform backups, move data between clients/year or create specially formatted data to upload/send for tax filings or other law related matters.

Then there was a subtly named function, used in this way

CASE ISCHAR( LastKey() ) .OR. LastKey() = K_ENTER

I quickly, and wrongly, converted ISCHAR to HB_ISCHAR. Wrong, wrong, wrong !
Since Lastkey() always returns a numeric value, HB_ISCHAR will be always .F. Instead, the real job of the function is to return .T. if the pressed key is a "char", a letter of the alphabet. I will check better in the following lines to understand if the Lastkey() value is used for some other job, in the meantime I created this:

FUNCTION ischar( nValue )
   IF nValue >= 32 .and. nValue <= 128
      RETURN .T.
   ENDIF
   RETURN .F.

Another function I found is Funcky ReadScreen(): it returns the string at passed screen coordinates. It's used only once and I plan to replace it, when I will look at that code. At the moment I just exit the application.

FUNCTION ReadScreen()
   ? "Function ReadScreen not implemented"
   QUIT

Then there is one last function that I hope I can replace easily. This function is not listed in the blinker generated files. Looking at the few places where it is used, it seems to be a sort of "change directory": it is used in the sourcse that handle index creation and file copying. I will have to understand it better. In the meantime I will create a stub function that prints a debug line and returns .T.

To have my program compile and link I had to add a couple of functions from the decompiled executables, to write 6 application functions and some more Funcky stubs (see code below).

Of the 6 rewritten functions, one is related to licensing (that was already disabled in the code so the code could also be removed); another is a clone of pad, some are screen related, all created as stubs.

The first executable is now ready to be tested. The environment variable set to the root data directory, program launched....

Now I'm were I was a couple of days ago, but with clean, well formatted code, the process documented in this blog and some ideas on how to continue on the drawing board...

The program runs, the menu appears on screen... now the next step is to let the software find its data files...

During the compile/link/error/add_functions loop I added the following functions to funcky.prg

// get status of Clipper's various set commands
// PROBLEM: incomplete
FUNCTION Status( nIndex )
   DO CASE
   CASE nIndex == 6
      RETURN( _SET_CURSOR )
   END CASE
      clear screen
      ? "Richiesto status valore ", nIndex
      QUIT
  RETURN .F. 

// csrput() - place cursor at specified row/column
// PROBLEM: is it correct ?  
FUNCTION csrput( y, x )
   @ y,x SAY ""
   RETURN .T.


// palette() - access 64 colors on EGA and VGA adaptors
FUNCTION PALETTE()
   RETURN 1

FUNCTION SWAP( p1, p2 )
   LOCAL t := p1

   p1 := p2
   p2 := t
   RETURN .T.

2 thoughts on “The missing sources part 2

  1. Maurizio la Cecilia

    Hi Francesco,
    really an interesting and well done blog. A clear and comprehensive path to obtain an effective port.

    About your csrput() ported version I think that:
    1. the SAY “” is unnecessary and you can omit it at all;
    2. the @ y,x is always pre-processed to SetPos( y, x );
    3. the original FuncKy function returns NIL.
    So, I suggest this code:

    FUNCTION csrput( y, x )
       SetPos( y, x )
       return nil
    

    In addition, here is my ancient version of aMaxStrLen(), just to show that programming isn’t an exact science:

    FUNCTION aMaxStrLen( aArr )
    
       LOCAL nMaxLen := 0
    
       AEval( aArr, { | e | nMaxLen := Max( nMaxLen, IIf( HB_IsString( e ), Len( e ), 0 ) ) } )
       RETURN nMaxLen
    

    Waiting for the next post.
    Thanks a lot.
    Maurizio

    Reply
    1. Francesco Post author

      Thanks Maurizio.

      Yes, programming isn’t an exact science. And infact aMaxStrLen can be written in different ways… it would be nice to list some of them and analyze which is the best for speed and/or readability…

      I will comment on my version of the code in a dedicated post.

      Reply

Leave a Reply to Francesco Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>