;*****************************************************************************/
;*                                                                           */
;*                                   modload.s                               */
;*                                                                           */
;*                    o65 module loader for the cc65 library                 */
;*                                                                           */
;*                                                                           */
;*                                                                           */
;* (C) 2002      Ullrich von Bassewitz                                       */
;*               Wacholderweg 14                                             */
;*               D-70597 Stuttgart                                           */
;* EMail:        uz@musoftware.de                                            */
;*                                                                           */
;*                                                                           */
;* This software is provided 'as-is', without any expressed or implied       */
;* warranty.  In no event will the authors be held liable for any damages    */
;* arising from the use of this software.                                    */
;*                                                                           */
;* Permission is granted to anyone to use this software for any purpose,     */
;* including commercial applications, and to alter it and redistribute it    */
;* freely, subject to the following restrictions:                            */
;*                                                                           */
;* 1. The origin of this software must not be misrepresented; you must not   */
;*    claim that you wrote the original software. If you use this software   */
;*    in a product, an acknowledgment in the product documentation would be  */
;*    appreciated but is not required.                                       */
;* 2. Altered source versions must be plainly marked as such, and must not   */
;*    be misrepresented as being the original software.                      */
;* 3. This notice may not be removed or altered from any source              */
;*    distribution.                                                          */
;*                                                                           */
;*****************************************************************************/



        .include        "o65.inc"
        .include        "modload.inc"

        .import         pushax, pusha0, push0, push1, decax1
        .import         _malloc, _free, _bzero
        .import         __ZP_START__    ; Linker generated
        .importzp       sp, ptr1, tmp1, regbank

        .macpack        generic

;------------------------------------------------------------------------------
; Variables stored in the register bank in the zero page. Placing the variables
; here will protect them when calling other C functions.

Module          = regbank+0             ; Pointer to module memory
Ctrl            = regbank+2             ; Pointer to mod_ctrl structure
TPtr            = regbank+4             ; Pointer to module data for relocation

;------------------------------------------------------------------------------
; Static module data

.bss

; Save areas and error recovery data
Stack:          .byte   0               ; Old stackpointer
RegBankSave:    .res    6               ; Save area for register bank

; The header of the o65 file. Since we don't need the first 8 bytes any
; longer, once we've checked them, we will overlay them with other data to
; save a few bytes.
Header:         .tag    O65_HDR         ; The o65 header

; Input
InputByte       = Header                ; Byte read from input

; Relocation
RelocVal        = Header + 1            ; Relocation value

.data
Read:   jmp     $FFFF                   ; Jump to read routine

.rodata
ExpectedHdr:
        .byte   O65_MARKER_0, O65_MARKER_1              ; non C64 marker
        .byte   O65_MAGIC_0, O65_MAGIC_1, O65_MAGIC_2   ; Magic ("o65")
        .byte   O65_VERSION                             ; Version
        .word   O65_MODE_CC65                           ; Mode word

ExpectedHdrSize = * - ExpectedHdr


;------------------------------------------------------------------------------
; PushCallerData: Push the callerdata member from control structure onto the
; C stack.

.code
PushCallerData:
        ldy     #MOD_CTRL::CALLERDATA+1
        lda     (Ctrl),y
        tax
        dey
        lda     (Ctrl),y
        jmp     pushax

;------------------------------------------------------------------------------
; RestoreRegBank: Restore the register bank contents from the save area. Will
;                 destroy A and X (the latter will be zero on return).

.code
RestoreRegBank:
        ldx     #6
@L1:    lda     RegBankSave-1,x
        sta     regbank-1,x
        dex
        bne     @L1
        rts

;------------------------------------------------------------------------------
; GetReloc: Return a relocation value based on the segment in A.
; The routine uses some knowledge about the values to make the code shorter.

.code
GetReloc:
        cmp     #O65_SEGID_TEXT
        bcc     FormatError
        cmp     #O65_SEGID_ZP
        beq     @L1
        bcs     FormatError

; Text, data and bss segment

        lda     Module
        ldx     Module+1                ; Return start address of buffer
        rts

; Zero page relocation

@L1:    lda     #<__ZP_START__
        ldx     #>__ZP_START__
        rts

;------------------------------------------------------------------------------
; ReadByte: Read one byte with error checking into InputByte and A.
; ReadAndCheckError: Call read with the current C stack and check for errors.

.bss
ReadSize:       .res    2

.code
ReadByte:

; C->read (C->callerdata, &B, 1)

        jsr     PushCallerData
        lda     #<InputByte
        ldx     #>InputByte
        jsr     pushax
        ldx     #0
        lda     #1

; This is a second entry point used by the other calls to Read

ReadAndCheckError:
        sta     ReadSize
        stx     ReadSize+1
        jsr     Read

; Check the return code and bail out in case of problems

        cmp     ReadSize
        bne     @L1
        cpx     ReadSize+1
        beq     @L2                     ; Jump if ok
@L1:    lda     #MLOAD_ERR_READ
        bne     CleanupAndExit

; Done

@L2:    lda     InputByte               ; If called ReadByte, load the byte read
Done:   rts

;------------------------------------------------------------------------------
; FormatError: Bail out with an o65 format error

.code
FormatError:
        lda     #MLOAD_ERR_FMT
;       bne     CleanupAndExit          ; Branch always

;------------------------------------------------------------------------------
; CleanupAndExit: Free any allocated resources, restore the stack and return
;                 to the caller.

.code
CleanupAndExit:

; Restore the stack so we may return to the caller from here

        ldx     Stack
        txs

; Save the error return code

        pha

; Check if we have to free the allocated block

        lda     Module
        ldx     Module+1
        bne     @L1
        tay                             ; Test high byte
        beq     @L2
@L1:    jsr     _free                   ; Free the allocated block

; Restore the register bank

@L2:    jsr     RestoreRegBank

; Restore the  error code and return to the caller

        ldx     #$00                    ; Load the high byte
        pla
        rts

;------------------------------------------------------------------------------
; RelocSeg: Relocate the segment pointed to by a/x

.code
RelocSeg:
        jsr     decax1                  ; Start value is segment-1
        sta     TPtr
        stx     TPtr+1

Loop:   jsr     ReadByte                ; Read byte from relocation table
        beq     Done                    ; Bail out if end of table reached

        cmp     #255                    ; Special offset?
        bne     @L1

; Increment offset by 254 and continue

        lda     TPtr
        add     #254
        sta     TPtr
        bcc     Loop
        inc     TPtr+1
        jmp     Loop

; Increment offset by A

@L1:    add     TPtr
        sta     TPtr
        bcc     @L2
        inc     TPtr+1

; Read the relocation byte, extract the segment id, fetch the corresponding
; relocation value and place it into ptr1

@L2:    jsr     ReadByte
        and     #O65_SEGID_MASK
        jsr     GetReloc
        sta     RelocVal
        stx     RelocVal+1

; Get the relocation byte again, this time extract the relocation type.

        lda     InputByte
        and     #O65_RTYPE_MASK

; Check for and handle the different relocation types.

        cmp     #O65_RTYPE_WORD
        beq     RelocWord
        cmp     #O65_RTYPE_HIGH
        beq     RelocHigh
        cmp     #O65_RTYPE_LOW
        bne     FormatError

; Relocate the low byte

RelocLow:
        ldy     #0
        clc
        lda     RelocVal
        bcc     AddCommon

; Relocate a high byte

RelocHigh:
        jsr     ReadByte                ; Read low byte from relocation table
        ldy     #0
        clc
        adc     RelocVal                ; We just need the carry
AddHigh:
        lda     RelocVal+1
AddCommon:
        adc     (TPtr),y
        sta     (TPtr),y
        jmp     Loop                    ; Done, next entry

; Relocate a word

RelocWord:
        ldy     #0
        clc
        lda     RelocVal
        adc     (TPtr),y
        sta     (TPtr),y
        iny
        bne     AddHigh                 ; Branch always (add high byte)

;------------------------------------------------------------------------------
; mod_load: Load and relocate an o65 module

.code
_mod_load:

; Save the register bank and clear the Module pointer

        pha
        ldy     #6
@L1:    lda     regbank-1,y
        sta     RegBankSave-1,y
        dey
        bne     @L1
        sty     Module
        sty     Module+1
        pla

; Save the passed parameter

        sta     Ctrl
        stx     Ctrl+1

; Save the stack pointer so we can bail out even from subroutines

        tsx
        stx     Stack

; Get the read function pointer from the control structure and place it into
; our call vector

        ldy     #MOD_CTRL::READ
        lda     (Ctrl),y
        sta     Read+1
        iny
        lda     (Ctrl),y
        sta     Read+2

; Read the o65 header: C->read (C->callerdata, &H, sizeof (H))

        jsr     PushCallerData
        lda     #<Header
        ldx     #>Header
        jsr     pushax
        lda     #.sizeof(O65_HDR)
        ldx     #0                      ; Always less than 256
        jsr     ReadAndCheckError       ; Bails out in case of errors

; We read the o65 header successfully. Validate it.

        ldy     #ExpectedHdrSize-1
ValidateHeader:
        lda     Header,y
        cmp     ExpectedHdr,y
        bne     HeaderError
        dey
        bpl     ValidateHeader

; Header is ok as far as we can say now. Read all options, check for the
; OS option and ignore all others. The OS option contains a version number
; and the module id as additional data.

        iny                             ; Y = $00
        sty     TPtr+1                  ; Flag for OS option read
Opt:    jsr     ReadByte                ; Read the length byte
        beq     OptDone                 ; Jump if done
        sta     TPtr                    ; Use TPtr as a counter

; An option has a length of at least 2 bytes

        cmp     #2
        bcc     HeaderError             ; Must be 2 bytes total at least

; Check for the OS option

        dec     TPtr
        jsr     ReadByte                ; Get the option type
        cmp     #O65_OPT_OS             ; OS option?
        bne     SkipOpt                 ; No: Skip

        lda     TPtr                    ; Get remaining length+1
        cmp     #5                      ; CC65 has 6 bytes total
        bne     OSError

        jsr     ReadByte                ; Get the operating system
        cmp     #O65_OS_CC65
        bne     OSError                 ; Wrong operating system

        jsr     ReadByte                ; Get the version number, expect zero
        bne     OSError                 ; Wrong version

        jsr     ReadByte                ; Get low byte of id
        ldy     #MOD_CTRL::MODULE_ID
        sta     (Ctrl),y
        jsr     ReadByte
        ldy     #MOD_CTRL::MODULE_ID+1
        sta     (Ctrl),y

        inc     TPtr+1                  ; Remember that we got the OS

        jmp     Opt

; Skip one option

SkipOpt:
        dec     TPtr
        beq     Opt                     ; Next option
        jsr     ReadByte                ; Skip one byte
        jmp     SkipOpt

; Operating system error

OSError:
        lda     #MLOAD_ERR_OS
        jmp     CleanupAndExit

; Options done, check that we got the OS option

OptDone:
        lda     TPtr+1
        bne     CalcSizes

; Entry point for header errors

HeaderError:
        lda     #MLOAD_ERR_HDR
        jmp     CleanupAndExit

; Skipped all options. Calculate the size of text+data and of text+data+bss
; (the latter is the size of the memory block we need). We will store the
; total module size also into the control structure for evaluation by the
; caller

CalcSizes:
        lda     Header + O65_HDR::TLEN
        add     Header + O65_HDR::DLEN
        sta     TPtr
        lda     Header + O65_HDR::TLEN + 1
        adc     Header + O65_HDR::DLEN + 1
        sta     TPtr+1
        lda     TPtr
        add     Header + O65_HDR::BLEN
        pha                             ; Save low byte of total size
        ldy     #MOD_CTRL::MODULE_SIZE
        sta     (Ctrl),y
        lda     TPtr+1
        adc     Header + O65_HDR::BLEN + 1
        iny
        sta     (Ctrl),y
        tax
        pla                             ; Restore low byte of total size

; Total memory size is now in a/x. Allocate memory and remember the result,
; both, locally and in the control structure so it the caller can access
; the memory block. After that, check if we got the requested memory.

        jsr     _malloc
        sta     Module
        stx     Module+1

        ldy     #MOD_CTRL::MODULE
        sta     (Ctrl),y
        txa
        iny
        sta     (Ctrl),y
        ora     Module
        bne     GotMem

; Could not allocate memory

        lda     #MLOAD_ERR_MEM
        jmp     CleanupAndExit

; Control structure is complete now. Clear the bss segment.
; bzero (bss_addr, bss_size)

GotMem: lda     Module
        add     TPtr
        pha
        lda     Module+1
        adc     TPtr+1                  ; Module + tlen + dlen
        tax
        pla
        jsr     pushax
        lda     Header + O65_HDR::BLEN
        ldx     Header + O65_HDR::BLEN+1
        jsr     _bzero                  ; bzero (bss, bss_size);

; Load code and data segment into memory. The sum of the sizes of
; code+data segment is still in TPtr.
; C->read (C->callerdata, C->module, H.tlen + H.dlen)

        jsr     PushCallerData
        lda     Module
        ldx     Module+1
        jsr     pushax
        lda     TPtr
        ldx     TPtr+1
        jsr     ReadAndCheckError       ; Bails out in case of errors

; We've got the code and data segments in memory. Next section contains
; undefined references which we don't support. So check if the count of
; undefined references is actually zero.

        jsr     ReadByte
        bne     Undef
        jsr     ReadByte
        beq     Reloc
Undef:  jmp     FormatError

; Number of undefined references was zero. Next come the relocation tables
; for code and data segment. Relocate the code segment

Reloc:  lda     Module
        ldx     Module + 1              ; Code segment address
        jsr     RelocSeg

; Relocate the data segment

        lda     Module
        add     Header + O65_HDR::TLEN
        pha
        lda     Module + 1
        adc     Header + O65_HDR::TLEN + 1
        tax
        pla                             ; Data segment address in a/x
        jsr     RelocSeg

; We're done. Restore the register bank and return a success code

        jsr     RestoreRegBank          ; X will be zero on return
        lda     #MLOAD_OK
        rts

