; _______________________________________________________________________________________
;|                                                                                       |
;| ..::FreshLib::..  Free, open source. Licensed under "Fresh artistic license."         |
;|_______________________________________________________________________________________|
;
;  Description: OS independent micro configuration files library.
;
;  Target OS: Any
;
;  Dependencies: memory.asm; strlib.asm; arrays.asm; files.asm
;
;  Notes:
;
;_________________________________________________________________________________________
module "uConfig library"


; Structure of the file of uConfig:
;
; offset  | size  |   description
; --------+-------+-----------------------------------------------------------------------
;   0     |  4    |   Signature
;   4     |  4    |   $0a1a0a0d (CR, LF, EOF, 00)
;   8     |  4    |   Hash of the whole file including file length of the next field
;   12    |  4    |   Chunk data length (N)
;   16    |  N    |   Data chunks
; --------+-------+-----------------------------------------------------------------------
;
; Every data chunk have following structure:
;
; offset  | size  |   description
; --------+-------+-----------------------------------------------------------------------
;    0    |  4    |  Key name 4xASCII chars.
;    4    |  4    |  Length of the data in bytes (K)
;    8    |  4    |  Data type. Constant of cdtXXXXX (see definitions below)
;   12    |  K    |  Chunk data.
; --------+-------+-----------------------------------------------------------------------
;


cdtNULL     = 0
cdtInteger  = 1   ; 32bit integer value.
cdtString   = 2   ; utf-8 string.
cdtBlob     = 3   ; arbitraty sized array of bytes.
cdtConfig   = 4

cdtMaxAlowed = cdtConfig

struct TConfigHeader
  .signature dd ?
  .filler    dd ?
  .hash      dd ?
  .length    dd ?
  .chunks:
ends


struct TChunkHeader
  .KeyName dd ?
  .length  dd ?         ; length of the data without the size of the header and checksum.
  .type    dd ?
ends


struct TConfigRecord
  .KeyName  dd ?
  .DataSize dd ?
  .Type     dd ?
  .Data     dd ?
ends


; loads the file representation from the memory and expands it to the database tree structure.
; returns pointer to TArray of TConfigRecord structures.
proc LoadConfigDB, .ptrSource, .signature
begin
        push    ecx edx esi

        mov     esi, [.ptrSource]
        test    esi, esi
        jnz     .process_source

        stdcall CreateArray, sizeof.TConfigRecord
        jmp     .db_ok

.process_source:
; check 8 bytes signature
        mov     edx, [esi+TConfigHeader.signature]

        cmp     [.signature], -1
        je      .signatureok

        cmp     edx, [.signature]
        jne     .error_bad_signature

.signatureok:
        mov     [.signature], edx

        cmp     dword [esi+TConfigHeader.filler], $001a0a0d
        jne     .error_bad_signature

        mov     ecx, [esi+TConfigHeader.length]
        lea     esi, [esi+TConfigHeader.length]
        add     ecx, 4                                  ;(the length itself)
        stdcall DataHash, esi, ecx
        cmp     eax, [esi-4]
        jne     .error_bad_hash

        add     esi, 4
        sub     ecx, 4
        stdcall __DoRecurseConfigSource, esi, ecx

.db_ok:
        pushd   [.signature]
        popd    [eax+TArray.lparam]     ; store the signature to the .lparam field of the root TArray
        clc

.finish:
        pop     esi edx ecx
        return

.error_bad_signature:
        mov     eax, -1
        stc
        jmp     .finish

.error_bad_hash:
        mov     eax, -2
        stc
        jmp     .finish
endp



proc __DoRecurseConfigSource, .ptrSource, .length
.array  dd ?
begin
        pushad

        stdcall CreateArray, sizeof.TConfigRecord
        mov     [.array], eax

        mov     esi, [.ptrSource]
        mov     ecx, [.length]

.chunk_loop:
        cmp     ecx, sizeof.TConfigRecord
        jb      .finish

        stdcall AddArrayItems, [.array], 1
        mov     [.array], edx
        mov     edi, eax

        mov     eax, [esi+TChunkHeader.KeyName]
        mov     edx, [esi+TChunkHeader.length]
        mov     [edi+TConfigRecord.KeyName], eax
        mov     [edi+TConfigRecord.DataSize], edx

        mov     eax, [esi+TChunkHeader.type]
        mov     [edi+TConfigRecord.Type], eax

        add     esi, sizeof.TChunkHeader
        sub     ecx, sizeof.TChunkHeader

        and     eax, $7
        movzx   eax, [.type_handlers+eax]
        add     eax, .type_handlers
        call    eax
        mov     [edi+TConfigRecord.Data], eax

.next_chunk:
        mov     eax, [edi+TConfigRecord.DataSize]
        add     eax, 3
        and     al, $fc

        add     esi, eax
        sub     ecx, eax
        jmp     .chunk_loop

.finish:
        popad
        mov     eax, [.array]
        return

.type_handlers db .handle_null   - .type_handlers
               db .handle_int    - .type_handlers
               db .handle_string - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_config - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_blob   - .type_handlers

.handle_config:
        stdcall __DoRecurseConfigSource, esi, edx
.handle_null:
        retn

.handle_int:
        mov     eax, [esi]
        retn

.handle_string:
        stdcall StrNew
        stdcall StrCopyPart, eax, esi, 0, edx
        retn

.handle_blob:
        stdcall   GetMem, edx
        push      esi edi ecx
        mov       edi, eax
        mov       ecx, edx
        rep movsb
        pop       ecx edi esi
        retn
endp


; creates memory image (TArray) of the file of the config database.

proc SaveConfigFile, .ptrRoot, .signature
begin
        push    edx ecx

        stdcall CreateArray, 4
        mov     edx, eax

        mov     ecx, [.ptrRoot]
        stdcall AddArrayItems, edx, sizeof.TConfigHeader/4
        pushd   [.signature] $001a0a0d
        popd    [eax+TConfigHeader.filler] [eax+TConfigHeader.signature]

        stdcall __DoSaveParamArray, edx, [.ptrRoot]
        push    edx

        mov     ecx, [edx+TArray.count]
        shl     ecx, 2
        lea     edx, [edx+TArray.array+TConfigHeader.length]
        sub     ecx, sizeof.TConfigHeader
        mov     [edx], ecx
        add     ecx, 4 ; .length field
        stdcall DataHash, edx, ecx
        mov     [edx-4], eax            ; -4 if the offset to TConfigHeader.hash relative to TConfigHeader.length

        pop     eax
        pop     ecx edx
        return
endp



proc __DoSaveParamArray, .stream, .array
.count dd ?
begin
        pushad

        mov     ebx, [.array]
        mov     ecx, [ebx+TArray.count]
        lea     ebx, [ebx+TArray.array-sizeof.TConfigRecord]
        mov     [.count], ecx

.loop:
        add     ebx, sizeof.TConfigRecord
        dec     [.count]
        js      .end_save

        stdcall AddArrayItems, [.stream], 3
        mov     [.stream], edx

        pushd   [ebx+TConfigRecord.KeyName] [ebx+TConfigRecord.DataSize] [ebx+TConfigRecord.Type]
        popd    [eax+TChunkHeader.type] [eax+TChunkHeader.length] [eax+TChunkHeader.KeyName]

        movzx   eax, byte [ebx+TConfigRecord.Type]
        and     al, $07
        movzx   eax, [.type_handlers+eax]
        add     eax, .type_handlers
        call    eax
        jc      .loop

; align to dword
        mov     ecx, [ebx+TConfigRecord.DataSize]
        add     ecx, 3
        and     cl, $fc
        shr     ecx, 2
        stdcall AddArrayItems, [.stream], ecx
        mov     [.stream], edx
        mov     edi, eax

        rep movsd
        jmp     .loop

.end_save:
        popad
        mov     edx, [.stream]
        return

.type_handlers db .handle_null   - .type_handlers
               db .handle_int    - .type_handlers
               db .handle_string - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_config - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_blob   - .type_handlers

.handle_int:
        lea     esi, [ebx+TConfigRecord.Data]
        clc
        retn

.handle_string:
        stdcall StrPtr, [ebx+TConfigRecord.Data]
        mov     esi, eax
        clc
        retn

.handle_blob:
        mov     esi, [ebx+TConfigRecord.Data]
        clc
        retn

.handle_config:
        mov     eax, [.stream]
        mov     eax, [eax+TArray.count]
        push    eax
        stdcall __DoSaveParamArray, [.stream], [ebx+TConfigRecord.Data]
        mov     [.stream], edx

        pop     eax     ; old size
        mov     ecx, [edx+TArray.count]
        sub     ecx, eax
        lea     edx, [edx+TArray.array+4*eax-sizeof.TChunkHeader]
        shl     ecx, 2
        mov     [edx+TChunkHeader.length], ecx

.handle_null:
        stc
        retn
endp



proc FreeConfigDB, .ptrRoot
.dummy TConfigRecord
begin
        push    eax

        cmp     [.ptrRoot], 0
        je      .exit

        mov     [.dummy.Type], cdtConfig
        mov     eax, [.ptrRoot]
        mov     [.dummy.Data], eax

        lea     eax, [.dummy]
        stdcall __FreeConfigRecord, eax

.exit:
        pop     eax
        return
endp



; searches the tree for the given path and returns a pointer to the variable, containing pointer to TArray of the directory.
proc __GetParamArray, .ptrVarRoot, .pDirectory, .create
begin
        push    ebx edx esi

        mov     esi, [.pDirectory]
        mov     ebx, [.ptrVarRoot]

        test    esi, esi
        jz      .found

.loop:
        cmp     dword [esi], 0
        je      .found

        stdcall __ScanParamArray, [ebx], [esi]
        jc      .err_not_found
        cmp     [eax+TConfigRecord.Type], cdtConfig
        jne     .err_not_directory

.next:
        lea     ebx, [eax+TConfigRecord.Data]
        add     esi, 4
        jmp     .loop

.found:
        mov      eax, ebx
        clc
        pop     esi edx ebx
        return

.err_not_found:
        cmp     [.create], 0
        je      .no_create

        stdcall AddArrayItems, [ebx], 1
        mov     [ebx], edx
        mov     edx, eax
        pushd   [esi]
        popd    [edx+TConfigRecord.KeyName]
        mov     [edx+TConfigRecord.Type], cdtConfig
        stdcall CreateArray, sizeof.TConfigRecord
        mov     [edx+TConfigRecord.Data], eax
        mov     eax, edx
        jmp     .next

.no_create:
        xor     eax, eax
.error:
        stc
        pop     esi edx ebx
        return

.err_not_directory:
        xor     eax, eax
        dec     eax
        jmp     .error
endp




; returns:
;   CF=0 and eax = pointer to TConfigRecord
;            ecx = index of the record in the array.
;   CF=1 and eax = 0 - the key was not found.
proc __ScanParamArray, .ptrArray, .name
begin
        push    ecx esi
        mov     esi, [.ptrArray]
        test    esi, esi
        jz      .not_found

        mov     ecx, [esi+TArray.count]
        lea     esi, [esi+TArray.array]
        jecxz   .not_found

        xor     edx, edx
.loop:
        mov     eax, [esi+TConfigRecord.KeyName]
        cmp     eax, [.name]
        je      .found

        add     esi, sizeof.TConfigRecord
        inc     edx
        loop    .loop

.not_found:
        xor     eax, eax
        stc
        pop     esi ecx
        return

.found:
        mov     eax, esi
        clc
        pop     esi ecx
        return
endp


; returns
;   eax - poiner to the TConfigRecord or NULL if missing
proc GetConfigParam, .ptrConfig, .pDirectory, .name
begin
        push    edx
        lea     eax, [.ptrConfig]
        stdcall __GetParamArray, eax, [.pDirectory], 0
        stdcall __ScanParamArray, [eax], [.name]
        pop     edx
        return
endp


proc GetConfigParam.AsString, .ptrConfig, .pDirectory, .name
begin
        push    edx

        stdcall GetConfigParam, [.ptrConfig], [.pDirectory], [.name]
        jc      .finish

        movzx   edx, byte [eax+TConfigRecord.Type]
        and     dl, $07
        movzx   edx, [.type_handlers+edx]
        add     edx, .type_handlers
        call    edx

.finish:
        pop     edx
        return

.type_handlers db .handle_null   - .type_handlers
               db .handle_int    - .type_handlers
               db .handle_string - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_config - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_blob   - .type_handlers

.handle_null:
        stdcall StrDup, .txtNULL
        retn

.handle_int:
        stdcall NumToStr, [eax+TConfigRecord.Data], ntsSigned or ntsDec
        retn

.handle_string:
        stdcall StrDup, [eax+TConfigRecord.Data]
        retn

.handle_config:
        stdcall StrDup, .txtDir
        retn

.handle_blob:
        stdcall StrDup, .txtBlob
        retn

.txtNULL db 'NULL', 0
.txtDir  db 'SDIR', 0
.txtBlob db 'BLOB', 0

endp



proc __FreeConfigRecord, .ptrRecord
begin
        pushad

        mov     esi, [.ptrRecord]
        movzx   eax, byte [esi+TConfigRecord.Type]

        and     al, $07
        mov     al, [.type_handlers+eax]
        add     eax, .type_handlers
        call    eax

        xor     eax, eax
        mov     [esi+TConfigRecord.Type], eax
        mov     [esi+TConfigRecord.Data], eax
        mov     [esi+TConfigRecord.DataSize], eax
        popad
        return

.type_handlers db .handle_null   - .type_handlers
               db .handle_int    - .type_handlers
               db .handle_string - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_config - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_blob   - .type_handlers

.handle_config:
        mov     eax, [esi+TConfigRecord.Data]
        mov     ecx, [eax+TArray.count]
        lea     eax, [eax+TArray.array]
        jecxz   .handle_blob

.loop:
        stdcall __FreeConfigRecord, eax
        add     eax, sizeof.TConfigRecord
        loop    .loop

.handle_blob:
        stdcall FreeMem, [esi+TConfigRecord.Data]
        retn

.handle_string:
        stdcall StrDel, [esi+TConfigRecord.Data]
.handle_null:
.handle_int:
        retn
endp





proc DelCongigParam, .ptrVarConfig, .pDirectory, .name
begin
        pushad

        stdcall __GetParamArray, [.ptrVarConfig], [.pDirectory], 0
        jc      .deleted
        mov     esi, eax

        stdcall __ScanParamArray, [esi], [.name]
        jc      .deleted

        stdcall __FreeConfigRecord, eax
        stdcall DeleteArrayItems, [esi], edx, 1
        mov     [esi], edx

.deleted:
        popad
        return
endp


; set the value of the given config parameter.
; if the parameter exists, the value will be changed.
; if the parameter does not exists, it will be created.
proc SetConfigParam, .ptrVarConfig, .pDirectory, .name, .type, .value, .size
begin
        push    eax ecx edx esi edi

        stdcall __GetParamArray, [.ptrVarConfig], [.pDirectory], 1
        jc      .missing_directory

        mov     esi, eax

        stdcall __ScanParamArray, [esi], [.name]
        jnc     .record_ok

        stdcall AddArrayItems, [esi], 1
        mov     [esi], edx
        push    [.name]
        pop     [eax+TConfigRecord.KeyName]

.record_ok:
        mov     esi, eax

        stdcall __FreeConfigRecord, esi

        movzx   ecx, byte [.type]
        and     cl, $7
        mov     [esi+TConfigRecord.Type], ecx
        movzx   ecx, [.type_handlers+ecx]
        add     ecx, .type_handlers

        call    ecx
        clc
.finish:
        pop     edi esi edx ecx eax
        return

.missing_directory:
        stc
        jmp     .finish

.type_handlers db .handle_null   - .type_handlers
               db .handle_int    - .type_handlers
               db .handle_string - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_config - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_blob   - .type_handlers
               db .handle_blob   - .type_handlers

.handle_int:
        mov     [esi+TConfigRecord.DataSize], 4
        push    [.value]
        pop     [esi+TConfigRecord.Data]

.handle_null:
        retn

.handle_string:
        stdcall StrDup, [.value]
        mov     [esi+TConfigRecord.Data], eax
        stdcall StrLen, eax
        mov     [esi+TConfigRecord.DataSize], eax
        retn

.handle_config:
        stdcall CreateArray, sizeof.TConfigRecord
        mov     [esi+TConfigRecord.Data], eax
        mov     [esi+TConfigRecord.DataSize], 4
        retn

.handle_blob:
        mov     ecx, [.size]
        stdcall GetMem, ecx
        mov     [esi+TConfigRecord.Data], eax
        mov     [esi+TConfigRecord.DataSize], ecx

        push    esi
        mov     edi, eax
        mov     esi, [.value]
        rep movsb
        pop     esi
        retn
endp




endmodule
