;  
; CFGEN.PB  
; based on a MMBasic(BOS) version by PeterM of 'TheBackShed'
; '    V1.1 Improved algorithms thanks to TZ
; '    V1.11 Tidy Up
; '

EnableExplicit

Enumeration
  #MainWin
  #MainEditor
  #FileIn
  #FileOut
  #Courier
  #Bmerge
  #Bjoin
  #Bredo
  #Bhelp
  
EndEnumeration

Declare.q subtract(b$, c$)
Declare.s getjumpaddress(a$, text_add$)
Declare.s getadd()
Declare.q getnum4()
Declare.l getnum2()
Declare.s increment4(value$)
Declare.s subaddress(p$,b)
Declare.l domerge()
Declare.l dojoin()
Declare.l domake(mode.l)
Declare.l doagain()
Declare.l dohelp()

Global Dim symbols$(100,2)
Global inname$, cname$, displayname$
Global TB.s 
TB="    " ; tab = 4 spaces

If OpenWindow( #MainWin, 0, 0, 780, 640, "EditorGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget )
  EditorGadget(#MainEditor, 5, 35, 760, 600)
  
  ButtonGadget(#Bmerge, 5, 5, 140,25, "Merge")
  ButtonGadget(#Bjoin, 150, 5, 140,25, "Join")
  ButtonGadget(#Bredo, 300, 5, 140,25, "New")
  ButtonGadget(#Bhelp, 450, 5, 140,25, "Help")
  If LoadFont(#Courier,"Courier New",10)
    SetGadgetFont(#MainEditor, FontID(#Courier))   ; Set the font to monospaced
  EndIf
  BindGadgetEvent(#Bmerge, @domerge()) ; assign the buttons to procedures
  BindGadgetEvent(#Bjoin, @dojoin())
  BindGadgetEvent(#Bredo, @doagain())
  BindGadgetEvent(#Bhelp, @dohelp())
  
  doagain()
  
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow 
EndIf
End

Procedure.l dohelp()
  Protected txt.s
  txt= "Author - TassyJim 11 Sep 2015"
  txt=txt+#CRLF$+"Based on a MMBasic(DOS) program by PeterM at 'The Back Shed'"
  txt=txt+#CRLF$
  txt=txt+#CRLF$+ "When choosing the name of the file for the Basic output, "
  txt=txt+#CRLF$+"if you don't specify any extension, '.BAS' will be appended automatically."
  txt=txt+#CRLF$
  txt=txt+#CRLF$+"Choose 'Join' Or 'Merge' To define the output format"
  txt=txt+#CRLF$+"'Join' is typically used For individual CFunctions, each function in the .ELF file"
  txt=txt+#CRLF$+"is given its own header, all the CFunctions are output to a single file but can"
  txt=txt+#CRLF$+"then be copied and pasted as required. When 'Join' is chosen the 'main' function"
  txt=txt+#CRLF$+"is a dummy and is NOT included in the output."
  txt=txt+#CRLF$+"'Merge' is used For CFunctions such as drivers where one C function can"
  txt=txt+#CRLF$+"be called from another."
  txt=txt+#CRLF$
  txt=txt+#CRLF$+"You can rerun with the same input and destination files"
  txt=txt+#CRLF$+"by selecting 'Join' or 'Merge' again."
  txt=txt+#CRLF$
  txt=txt+#CRLF$+"'New' will allow you to select different source (ELF) and destination (BAS) files."
  txt=txt+#CRLF$
  txt=txt+#CRLF$+"As well as writing to the destination BAS file, the output is copied to the clipboard."
  MessageRequester("Help:",txt)
EndProcedure

Procedure.l doagain()
  ClearGadgetItems(#MainEditor)
  inname$= OpenFileRequester(".ELF file", "",  "ELF File|*.elf|All files (*.*)|*.*",0)
  If inname$<>""
    cname$ = SaveFileRequester(".BAS file", "",  "BAS File|*.bas|All files (*.*)|*.*",0)
    If cname$ = ""
      displayname$= "test"
    Else
      displayname$= GetFilePart(cname$)
      If FindString(displayname$,".")=0 ; if no file extension, make it a BAS file
        cname$=cname$+".bas"
      EndIf
      displayname$=StringField(displayname$,1,".")
    EndIf
    
    AddGadgetItem(#MainEditor, -1,"'  processing "+inname$)
    AddGadgetItem(#MainEditor, -1,"'  saving to  "+cname$)
    AddGadgetItem(#MainEditor, -1," ")
    AddGadgetItem(#MainEditor, -1,"'  Select Merge or Join to start processing")
    AddGadgetItem(#MainEditor, -1," ")
  EndIf
  
EndProcedure

Procedure.l dojoin()
  domake(0)
EndProcedure

Procedure.l domerge()
  domake(1)
EndProcedure

Procedure.l domake(mode.l)
  Protected re.l, by.l, m.l, n.l, i.l, j.l, k.q, l.l
  Protected s$, d$, a$, filelength.q
  Protected section_header, section_header_size, section_header_num, text_section, text_position, text_size, text_link
  Protected string_table_sector, string_table, symtab_section, strtab_section
  Protected symtab_offset, symtab_num, symstr_offset, first, text_add$, main_offset$, main$, lineoftext.s
  Protected currentfunction.l, cfunbas.s, monthtxt.s, timestamp.s, dat$, branchoffset.l, branch$
  
  If cname$<>"" ; if output file selection was cancelled, still allow coversion to run
    Re = OpenFile(#FileOut, cname$)
  EndIf
  If inname$ <>"" ; if input file selection was cancelled, do nothing.
    Re = OpenFile(#FileIn, inname$)
    filelength = Lof(#FileIn) ; needed for seek overflow check
    ClearGadgetItems(#MainEditor)
    AddGadgetItem(#MainEditor, -1,"'  processing "+inname$)
    AddGadgetItem(#MainEditor, -1,"'  saving to  "+cname$)
    AddGadgetItem(#MainEditor, -1," ")
    If   mode=1
      AddGadgetItem(#MainEditor, -1,"'  Using merge Mode" )
    Else
      mode = 0
      AddGadgetItem(#MainEditor,-1, "'  Using join Mode")
    EndIf
    AddGadgetItem(#MainEditor, -1," ")
    first=1 ;'first time into the various loops
    For n = 1 To 32 ; skip 32 bytes
      by=ReadByte(#FileIn)
    Next n
    section_header=getnum4() 
    For n = 1 To 10 ; skip 10 bytes
      by=ReadByte(#FileIn)
    Next n
    section_header_size=getnum2()
    section_header_num=getnum2()
    string_table_sector=getnum2()
    i=section_header+ (Section_header_size*string_table_sector) + 16 ;'get to the offset in the string table sector
    FileSeek( #FileIn,i)
    string_table= getnum4() ;'get the offset to the string table
    AddGadgetItem(#MainEditor, -1,"'  Functions found:")
    AddGadgetItem(#MainEditor,-1,"'  Address"+TB+"Function")
    For j=0 To section_header_num-1
      i=section_header+ (Section_header_size*j) ;'get to the next sector header
      FileSeek( #FileIn,i)                      ;'point to the .text section header
      k=getnum4()
      FileSeek( #FileIn,k+string_table) ;'point to the name string for this sector
      s$=""
      d$=Chr(ReadAsciiCharacter(#FileIn))
      While Asc(d$)<>0 ;'read in the zero terminated string
        s$=s$+d$
        d$=Chr(ReadAsciiCharacter(#FileIn))
      Wend
      If s$=".text" And text_section=0 ;'store text sector number
        text_section=j 
      EndIf
      If s$=".symtab" And symtab_section=0 ;'store symbol table sector number
        symtab_section=j 
      EndIf
      If s$=".strtab" And strtab_section=0 ;'store symbol table strings sector number
        strtab_section=j 
      EndIf
    Next j
    
    i=section_header+ (Section_header_size*text_section) ;'get to the .text offset 
    FileSeek( #FileIn,i)                                 ;'point to the .text section header
    For n = 1 To 12
      by=ReadByte(#FileIn)
    Next n
    text_add$=getadd()
    text_position=getnum4()
    text_size= getnum4()
    text_link= getnum4()
    i=section_header+ 16+ (Section_header_size*(symtab_section)) 
    FileSeek( #FileIn,i) ;'point to the symbol table section header
    symtab_offset=getnum4()
    symtab_num=getnum4()/16
    
    i=section_header + 16 + (Section_header_size*(strtab_section)) 
    FileSeek( #FileIn,i) ;'point to the string table section header
    symstr_offset=getnum4()
    m=0 ;'counter for the stored symbols array
    For i=0 To symtab_num-1 ;'loop through all the symbols
      FileSeek( #FileIn,symtab_offset+(16*i)) ;'point to the next symbol in the .symtab section 
      k=getnum4()
      a$=getadd()
      re = getnum4()
      l=ReadAsciiCharacter(#FileIn)
      If (k+symstr_offset) > filelength
        Break
      EndIf
      
      FileSeek( #FileIn,k+symstr_offset) ;'point to the symbol name
      s$=""
      d$=Chr(ReadAsciiCharacter(#FileIn))
      While Asc(d$)<>0 ;'read in the zero terminated string
        s$=s$+d$
        d$=Chr(ReadAsciiCharacter(#FileIn))
      Wend
      If (l=18) And (Left(s$,1)<>"_") ;'Only store function names
        symbols$(m,1)=a$
        symbols$(m,2)=s$
        AddGadgetItem(#MainEditor,-1,  "'  "+symbols$(m,1)+"  "+symbols$(m,2))
        m=m+1
        If s$="main" 
          main_offset$=a$
        EndIf
      EndIf
    Next i
    AddGadgetItem(#MainEditor,-1, "" )
    main$= Right("00000000" + Hex(subtract(Right(main_offset$,5), Right(text_add$,5))), 8) ;'calculate the offset  
    FileSeek( #FileIn,text_position)
    k=0
    lineoftext=TB
    currentfunction=0
    s$=text_add$
    cfunbas.s = ""
    monthtxt.s=Mid("xxJanFebMarAprMayJunJulAugSepOctNovDec",Month(Date())*3,3)
    timestamp.s="'  File "+displayname$+".bas  written "+FormatDate("%dd-", Date())+monthtxt+FormatDate("-%yyyy  %hh:%ii:%ss", Date())
    cfunbas=cfunbas+timestamp+#CRLF$+"'"
    AddGadgetItem(#MainEditor,-1,timestamp)
    AddGadgetItem(#MainEditor,-1,"'")
    For i=1 To text_size Step 4
      For j=0 To m-1
        If s$=symbols$(j,1) ;'address matches the start of a function
          currentfunction=j
          If (k<>0) And (mode=1)
            AddGadgetItem(#MainEditor,-1,lineoftext)
            cfunbas=cfunbas+#CRLF$+lineoftext
            lineoftext=TB
          EndIf
          If (k<>0)  And (mode=0) And (symbols$(j,2)<>"main")
            ;AddGadgetItem(#MainEditor,-1, "" )
            ;cfunbas=cfunbas+#CRLF$
            AddGadgetItem(#MainEditor,-1,lineoftext)
            cfunbas=cfunbas+#CRLF$+lineoftext
            lineoftext=TB
          EndIf
          k=0
          If (first=1) And (mode=1)  ;' First time in for a merged file output the Cfunction name
            AddGadgetItem(#MainEditor,-1, "CFunction "+displayname$)
            AddGadgetItem(#MainEditor, -1,TB+main$)
            cfunbas=cfunbas+#CRLF$+"CFunction "+displayname$+#CRLF$+TB+main$
          EndIf
          If mode=1 
            AddGadgetItem(#MainEditor,-1, TB+"'"+symbols$(j,2))
            cfunbas=cfunbas+#CRLF$+TB+"'"+symbols$(j,2)
          EndIf
          If (mode=0) And (symbols$(j,2)<>"main")
            If first = 0
              AddGadgetItem(#MainEditor,-1, "End CFunction")
              AddGadgetItem(#MainEditor, -1, "'")
              cfunbas=cfunbas+#CRLF$+"End CFunction"+#CRLF$+"'"
            EndIf
            AddGadgetItem(#MainEditor,-1, "CFunction "+symbols$(j,2))
            AddGadgetItem(#MainEditor, -1,TB+"00000000")
            cfunbas=cfunbas+#CRLF$+"CFunction "+symbols$(j,2)+#CRLF$+TB+"00000000"
          EndIf
          first=0
        EndIf
      Next j
      
      dat$=getadd()
      
      If Left(dat$,3)="0F4" ;'Jump instruction found convert to a branch
        a$=getjumpaddress(Right(dat$,5), text_add$) ;'this the address for the jump
        branchoffset=subtract(Right(s$,5),Right(a$,5))
        branch$=subaddress("0411FFFF",branchoffset)
        If (mode=0) And ( symbols$(currentfunction,2)<>"main")
          lineoftext=lineoftext+branch$+" "
        EndIf
        If mode =1
          lineoftext=lineoftext+branch$+" "
        EndIf
      Else
        If (mode=0) And (symbols$(currentfunction,2)<>"main" )
          lineoftext=lineoftext+dat$+" "
        EndIf
        If mode = 1
          lineoftext=lineoftext+dat$+" "
        EndIf
      EndIf
      k=k+1
      If k=8 ;'deal with line feed every eight words
        If (mode=0) And (symbols$(currentfunction,2)<>"main" )
          AddGadgetItem(#MainEditor,-1,lineoftext)
          cfunbas=cfunbas+#CRLF$+lineoftext
          lineoftext=TB
        EndIf
        
        If mode =1
          AddGadgetItem(#MainEditor,-1, lineoftext)
          cfunbas=cfunbas+#CRLF$+lineoftext
          lineoftext=TB
        EndIf
        k=0
      EndIf
      s$ = increment4(s$) ;'step on the program counter 
    Next i
    If k<>0 
      AddGadgetItem(#MainEditor, -1,lineoftext)
      cfunbas=cfunbas+#CRLF$+lineoftext
    EndIf
    
    AddGadgetItem(#MainEditor, -1,"End CFunction")
    AddGadgetItem(#MainEditor, -1," " )
    AddGadgetItem(#MainEditor, -1," " )
    cfunbas=cfunbas+#CRLF$+"End CFunction"+#CRLF$
    If IsFile(#FileOut) ; don't try to write to the output if it wasn't selected
      WriteString(#FileOut,cfunbas,#PB_Ascii)
      CloseFile(#FileOut)
    EndIf
    CloseFile(#FileIn)
    SetClipboardText(cfunbas) ; put the output onto the clipboard
  EndIf
  
EndProcedure

Procedure.q subtract(b$,c$)
  Protected f.q,g.q
  f=Val("$"+b$)
  g=Val("$"+c$)
  ProcedureReturn (f-g)/4
EndProcedure

Procedure.s getjumpaddress(a$,text_add$)
  Protected f
  f=Val("$"+a$)*4+1048576
  ProcedureReturn Left(text_add$,3)+Right(Hex(f),5)
EndProcedure

Procedure.s getadd()
  Protected a,b,c,d
  a=ReadAsciiCharacter(#FileIn)+256
  b=ReadAsciiCharacter(#FileIn)+256
  c=ReadAsciiCharacter(#FileIn)+256
  d=ReadAsciiCharacter(#FileIn)+256
  ProcedureReturn Right(Hex(d),2) +Right(Hex(c),2) +Right(Hex(b),2) +Right(Hex(a),2)
EndProcedure

Procedure.q getnum4()
  Protected a,b,c,d
  a=ReadAsciiCharacter(#FileIn)
  b=ReadAsciiCharacter(#FileIn)
  c=ReadAsciiCharacter(#FileIn)
  d=ReadAsciiCharacter(#FileIn)
  ProcedureReturn ((((d*256)+c)*256)+b)*256+a
EndProcedure

Procedure.l getnum2()
  Protected a,b
  a=ReadAsciiCharacter(#FileIn)
  b=ReadAsciiCharacter(#FileIn)
  ProcedureReturn b*256+a
EndProcedure

Procedure.s increment4(value$) 
  Protected i, v, NewValue$
  Protected Dim  V$(7)
  For i= 0 To 7: V$(i) = Mid(value$, i+1, 1): Next 
  V$(7) = Chr(Asc(V$(7)) + 4) 
  For i = 7 To 1 Step -1 
    v = Asc(V$(i))  
    If v > 70  ;' "F" 
      V$(i) = Chr(v - 23): V$(i-1) = Chr(Asc(V$(i-1)) + 1) 
    Else 
      If v >57 And v < 65 
        V$(i) = Chr(v + 7) ;' "9" "A" 
      EndIf
    EndIf 
    NewValue$ = V$(i)+ NewValue$ 
  Next 
  value$ = V$(0) + NewValue$ 
  ProcedureReturn value$
EndProcedure

Procedure.s subaddress(p$,b)
  Protected a
  a=Val("$"+Right(p$,4))+65536-b
  ProcedureReturn Left(p$,4)+Right(Hex(a),4)
EndProcedure
