Made it possible to load 8 sectors into ram
This commit is contained in:
parent
49230d4c72
commit
f5fe3a2172
3
build.sh
3
build.sh
|
@ -8,4 +8,5 @@ cd stage_2
|
||||||
./build.sh
|
./build.sh
|
||||||
cd ../
|
cd ../
|
||||||
|
|
||||||
|
cat stage_1/stage_1.bin > nightloader.bin
|
||||||
|
# cat stage_2/stage_2.bin >> nightloader.bin
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/env bash
|
#!/bin/env bash
|
||||||
|
|
||||||
|
qemu-system-x86_64 nightloader.bin
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
; TODO: Read FAT32 filesystem and load raw binary frompath nightloader/stage_2.bin
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
|
||||||
|
times 442 - ($-$$) db 0
|
||||||
|
db "####"
|
||||||
|
times 446 - ($-$$) db 0
|
||||||
|
|
||||||
|
mbr_table:
|
||||||
|
.part_1:
|
||||||
|
db 0x80 ; bootable flag
|
||||||
|
db 0, 2, 0 ; CHS - start-address (ordering: cylinder, sector, header)
|
||||||
|
db 0x9d
|
||||||
|
db 0, 16, 0 ; CHS - end-address (ordering: cylinder, sector, header)
|
||||||
|
dd 2
|
||||||
|
dd 16
|
||||||
|
|
||||||
|
.part_2:
|
||||||
|
db 0x00 ; bootable flag
|
||||||
|
db 0, 0, 0 ; CHS - start-address (ordering: cylinder, sector, header)
|
||||||
|
db 0x0
|
||||||
|
db 0, 0, 0 ; CHS - end-address (ordering: cylinder, sector, header)
|
||||||
|
dd 0
|
||||||
|
dd 0
|
||||||
|
|
||||||
|
.part_3:
|
||||||
|
db 0x00 ; bootable flag
|
||||||
|
db 0, 0, 0 ; CHS - start-address (ordering: cylinder, sector, header)
|
||||||
|
db 0x00
|
||||||
|
db 0, 0, 0 ; CHS - end-address (ordering: cylinder, sector, header)
|
||||||
|
dd 0
|
||||||
|
dd 0
|
||||||
|
|
||||||
|
.part_4:
|
||||||
|
db 0x00 ; bootable flag
|
||||||
|
db 0, 0, 0 ; CHS - start-address (ordering: cylinder, sector, header)
|
||||||
|
db 0x00
|
||||||
|
db 0, 0, 0 ; CHS - end-address (ordering: cylinder, sector, header)
|
||||||
|
dd 0
|
||||||
|
dd 0
|
||||||
|
|
||||||
|
.signature:
|
||||||
|
db 0x55, 0xaa
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
second_stage:
|
||||||
|
|
||||||
|
;mov si, finish_text
|
||||||
|
;mov dl, 36
|
||||||
|
;mov dh, 12
|
||||||
|
;mov cx, 8
|
||||||
|
|
||||||
|
; set the video mode correctly
|
||||||
|
xor ah, ah ; bios-function (set video mode)
|
||||||
|
mov al, 3 ; video mode with 80x25 characters
|
||||||
|
int 0x10
|
||||||
|
|
||||||
|
.happiness:
|
||||||
|
|
||||||
|
mov ah, 0x02 ; action: move cursor
|
||||||
|
mov bh, 0 ; page
|
||||||
|
mov dh, 1 ; row
|
||||||
|
mov dl, 1 ; column
|
||||||
|
int 0x10
|
||||||
|
|
||||||
|
mov ah, 0x0a ; action: write character
|
||||||
|
mov al, '$' ; character
|
||||||
|
mov bh, 0 ; page
|
||||||
|
mov cx, 13 ; count
|
||||||
|
int 10h
|
||||||
|
|
||||||
|
jmp .happiness
|
||||||
|
|
||||||
|
.prolog:
|
||||||
|
push di
|
||||||
|
push cx
|
||||||
|
push bx
|
||||||
|
push ax
|
||||||
|
|
||||||
|
xor di, di ; character index
|
||||||
|
|
||||||
|
.printing_loop:
|
||||||
|
|
||||||
|
.set_cursor_position:
|
||||||
|
|
||||||
|
.write_character:
|
||||||
|
|
||||||
|
push cx ; save len_buffer
|
||||||
|
|
||||||
|
; get the current character
|
||||||
|
mov bx, si
|
||||||
|
add bx, di
|
||||||
|
mov al, [bx]
|
||||||
|
; al now contains the current character
|
||||||
|
|
||||||
|
; set the other needed interrupt values
|
||||||
|
mov ah, 0x0a ; wanted function (write character at cursor position)
|
||||||
|
mov bh, 0 ; page number
|
||||||
|
mov cx, 1 ; repetitions of character
|
||||||
|
int 0x10 ; call bios basic draw functions
|
||||||
|
|
||||||
|
pop cx ; restore len_buffer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
inc dl ; go to next column
|
||||||
|
|
||||||
|
; go to a new line if needed
|
||||||
|
cmp dl, 80
|
||||||
|
jb .no_new_line_needed
|
||||||
|
xor dl, dl
|
||||||
|
inc dh ; increase column
|
||||||
|
|
||||||
|
.no_new_line_needed:
|
||||||
|
|
||||||
|
inc di ; go to next character
|
||||||
|
|
||||||
|
; go further if the string-printing hasn't been finished
|
||||||
|
cmp di, cx
|
||||||
|
jb .printing_loop
|
||||||
|
|
||||||
|
.epilog:
|
||||||
|
jmp .epilog
|
||||||
|
|
||||||
|
finish_text:
|
||||||
|
db "-FINISH-"
|
|
@ -1,210 +1,123 @@
|
||||||
|
|
||||||
section .text
|
|
||||||
org 0x7c00
|
|
||||||
start:
|
STAGE_1_CODE_MEMORY_ADDRESS equ 0x7c00
|
||||||
|
STAGE_1_STACK_MEMORY_ADDRESS equ 0x07e0
|
||||||
|
|
||||||
|
NIGHTLOADER_PARTITION_TYPE equ 0x9d
|
||||||
|
|
||||||
|
ORIGINAL_PARTITION_TABLE_OFFSET equ 446
|
||||||
|
ORIGINAL_NUM_PARTITIONS_IN_TABLE equ 4
|
||||||
|
ORIGINAL_PARTITION_ENTRY_SIZE equ 16
|
||||||
|
|
||||||
|
BOOTABLE_BYTE_OFFSET equ 0
|
||||||
|
CHS_START_SECTOR_ADDRESS_OFFSET equ 1
|
||||||
|
PARTITION_TYPE_OFFSET equ 4
|
||||||
|
CHS_END_SECTOR_ADDRESS equ 5
|
||||||
|
FIRST_LBA_SECTOR_ADDR_OFFSET equ 8
|
||||||
|
LAST_LBA_SECTOR_ADDR_OFFSET equ 12
|
||||||
|
|
||||||
|
org STAGE_1_CODE_MEMORY_ADDRESS
|
||||||
|
bits 16
|
||||||
|
stage_0:
|
||||||
|
|
||||||
mov ax, 0
|
mov bx, STAGE_1_STACK_MEMORY_ADDRESS
|
||||||
mov ds, ax
|
mov ss, bx
|
||||||
mov es, ax
|
mov bx, 0x0800
|
||||||
mov gs, ax
|
|
||||||
mov fs, ax
|
|
||||||
|
|
||||||
mov ax, 0x7e00
|
|
||||||
mov ss, ax
|
|
||||||
|
|
||||||
mov sp, 256
|
|
||||||
mov bp, 256
|
mov bp, 256
|
||||||
|
mov sp, 256
|
||||||
|
|
||||||
mov si, dx ; save the origin disk number
|
mov bx, 0x0000
|
||||||
|
mov ds, bx
|
||||||
|
mov es, bx
|
||||||
|
|
||||||
mov ax, 0x7c00
|
mov di, dx
|
||||||
add ax, 446
|
|
||||||
; ax now points to the start of the partition table
|
; set the video mode correctly
|
||||||
|
xor ah, ah ; bios-function (set video mode)
|
||||||
|
mov al, 3 ; video mode with 80x25 characters
|
||||||
|
int 0x10
|
||||||
|
|
||||||
xor di, di
|
|
||||||
|
|
||||||
call initialize_video_mode
|
|
||||||
|
|
||||||
.search_partition:
|
.search_partition:
|
||||||
|
|
||||||
mov bx, ax ; get the offset of the current entry
|
mov si, STAGE_1_CODE_MEMORY_ADDRESS
|
||||||
add bx, 4 ; go to the offset of the partition type
|
add si, ORIGINAL_PARTITION_TABLE_OFFSET
|
||||||
mov cl, [bx]
|
mov cx, 0 ; offset of the current entry from the partition table start
|
||||||
cmp cl, 0x9d
|
|
||||||
je .load_partition
|
|
||||||
|
|
||||||
inc di
|
|
||||||
|
|
||||||
; leave and print debug message if the last partition entry
|
|
||||||
; did not provide the wanted bootloader - partition
|
|
||||||
cmp di, 4
|
|
||||||
je .no_boot_partition
|
|
||||||
|
|
||||||
add ax, 16
|
.search_loop:
|
||||||
jmp .search_partition
|
|
||||||
|
mov bx, si
|
||||||
|
add bx, cx
|
||||||
|
add bx, PARTITION_TYPE_OFFSET
|
||||||
|
|
||||||
|
mov al, [bx]
|
||||||
|
|
||||||
|
cmp al, NIGHTLOADER_PARTITION_TYPE
|
||||||
|
je .load_partition;
|
||||||
|
|
||||||
|
; go to the next entry
|
||||||
|
add cx, ORIGINAL_PARTITION_ENTRY_SIZE
|
||||||
|
|
||||||
|
; check if there are more entries
|
||||||
|
cmp dx, ORIGINAL_NUM_PARTITIONS_IN_TABLE * ORIGINAL_PARTITION_ENTRY_SIZE
|
||||||
|
jb .search_loop
|
||||||
|
jmp .no_boot_partition
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.load_partition:
|
.load_partition:
|
||||||
; ax is the start of the bootloader's partition entry in the mbr
|
|
||||||
|
|
||||||
; read the chs-entry and push it onto the stack
|
mov si, cx ; address of the partition table entry of the nightloader-partition
|
||||||
|
|
||||||
; head
|
; TODO: Make this more flexible; that's urgent.
|
||||||
xor dx, dx
|
mov ah, 0x02 ; action (read from disk)
|
||||||
mov bx, ax
|
mov al, 8 ; num_sectors
|
||||||
add bx, 1
|
mov ch, 0 ; cylinder
|
||||||
mov dl, [bx]
|
mov cl, 2 ; sector (starts at 1, max.: 63)
|
||||||
push dx
|
mov dx, di ; storage device id
|
||||||
|
mov dh, 0 ; head
|
||||||
|
mov bx, 0x0800
|
||||||
|
mov es, bx ; read destination segment
|
||||||
|
mov bx, 0x0000 ; read destination address
|
||||||
|
int 0x13
|
||||||
|
|
||||||
; cylinder
|
|
||||||
xor dx, dx
|
|
||||||
mov bx, ax
|
|
||||||
add bx, 3
|
|
||||||
mov dh, [bx]
|
|
||||||
mov bx, ax
|
|
||||||
add bx, 2
|
|
||||||
mov dl, [bx]
|
|
||||||
shr dx, 6
|
|
||||||
push dx
|
|
||||||
|
|
||||||
; sector
|
|
||||||
xor dx, dx
|
|
||||||
mov bx, ax
|
|
||||||
add bx, 2
|
|
||||||
mov dl, [bx]
|
|
||||||
and dl, 0b00111111
|
|
||||||
push dx
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
; push the partition's length
|
|
||||||
|
|
||||||
mov bx, ax
|
|
||||||
add bx, 12
|
|
||||||
mov dx, [bx]
|
|
||||||
push dx ; lower part
|
|
||||||
|
|
||||||
mov bx, ax
|
|
||||||
add bx, 14
|
|
||||||
mov dx, [bx]
|
|
||||||
push dx ; higher part
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
; load it all and read from disk
|
|
||||||
|
|
||||||
mov bx, sp
|
|
||||||
add bx, 4
|
|
||||||
|
|
||||||
mov al, [bx] ; sector-count
|
|
||||||
|
|
||||||
add bx, 2
|
|
||||||
mov cl, [bx] ; start-sector
|
|
||||||
|
|
||||||
add bx, 2
|
|
||||||
mov ch, [bx] ; start-cylinder
|
|
||||||
|
|
||||||
mov dx, si ; drive
|
|
||||||
mov bx, 0x0800
|
|
||||||
mov es, bx ; output segment
|
|
||||||
|
|
||||||
mov bx, sp
|
|
||||||
add bx, 8
|
|
||||||
mov dh, [bx] ; start-head
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
call .write_hex
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
mov cl, 2
|
|
||||||
mov ah, 0x02
|
|
||||||
mov al, 1
|
|
||||||
mov bx, 0x0000 ; output address in es - segment
|
|
||||||
int 13h
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
mov cx, 8
|
|
||||||
mov si, 0x8000
|
mov si, 0x8000
|
||||||
|
mov cx, 8
|
||||||
mov dh, 12
|
mov dh, 12
|
||||||
mov dl, 32
|
mov dl, 36
|
||||||
call print_string
|
call print_string
|
||||||
jmp .keepalive
|
; jmp .wait
|
||||||
|
|
||||||
|
jmp 0x0800:0x0000
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.no_boot_partition:
|
.no_boot_partition:
|
||||||
|
|
||||||
mov cx, LEN_TEXT_NO_BOOT_PARTITION
|
mov si, errors.no_boot_partition
|
||||||
mov si, error_texts.no_boot_partition
|
mov cx, 30
|
||||||
|
mov dl, 25
|
||||||
mov dh, 12
|
mov dh, 12
|
||||||
mov dl, 32
|
|
||||||
call print_string
|
call print_string
|
||||||
|
|
||||||
.keepalive:
|
.wait:
|
||||||
jmp .keepalive
|
cli
|
||||||
|
hlt
|
||||||
.write_hex:
|
|
||||||
push bx
|
|
||||||
push cx
|
|
||||||
|
|
||||||
mov bx, error_texts.hextab
|
|
||||||
and cx, 0x0f
|
|
||||||
add bx, cx
|
|
||||||
mov cl, [bx]
|
|
||||||
mov bx, error_texts.hexbuf
|
|
||||||
inc bx
|
|
||||||
mov [bx], cl
|
|
||||||
|
|
||||||
mov bx, error_texts.hextab
|
|
||||||
and cx, 0xf0
|
|
||||||
shr cx, 4
|
|
||||||
add bx, cx
|
|
||||||
mov cl, [bx]
|
|
||||||
mov bx, error_texts.hexbuf
|
|
||||||
mov [bx], cl
|
|
||||||
|
|
||||||
mov cx, 2
|
|
||||||
mov si, error_texts.hexbuf
|
|
||||||
mov dh, 12
|
|
||||||
mov dl, 32
|
|
||||||
call print_string
|
|
||||||
|
|
||||||
pop cx
|
|
||||||
pop bx
|
|
||||||
|
|
||||||
ret
|
|
||||||
|
|
||||||
; %include "exfat.asm"
|
|
||||||
%include "gdt.asm"
|
|
||||||
%include "utility.asm"
|
|
||||||
|
|
||||||
LEN_TEXT_NO_BOOT_PARTITION equ 17
|
MESSAGE_LENGTH_NO_BOOT_PARTITION equ 30
|
||||||
|
|
||||||
error_texts:
|
errors:
|
||||||
|
|
||||||
.no_boot_partition:
|
.no_boot_partition:
|
||||||
;db "NO_BOOT_PARTITION"
|
db "NO NIGHTLOADER-PARTITION FOUND"
|
||||||
|
.hex:
|
||||||
.hextab:
|
|
||||||
db "0123456789abcdef"
|
db "0123456789abcdef"
|
||||||
|
|
||||||
.hexbuf:
|
%include "utility.asm"
|
||||||
resb 4
|
%include "fat32.asm"
|
||||||
|
%include "partition_table.asm"
|
||||||
|
|
||||||
times 446 - ($-$$) db 0
|
|
||||||
db 0x80
|
|
||||||
db 0, 2, 0
|
|
||||||
db 0x9d
|
|
||||||
db 0, 3, 0
|
|
||||||
dd 1
|
|
||||||
dd 1
|
|
||||||
|
|
||||||
times 510 - ($-$$) db 0
|
|
||||||
|
|
||||||
db 0x55
|
|
||||||
db 0xAA
|
|
||||||
|
|
||||||
db "################"
|
|
||||||
|
|
|
@ -1,22 +1,4 @@
|
||||||
|
|
||||||
section .text
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
initialize_video_mode:
|
|
||||||
|
|
||||||
.prolog:
|
|
||||||
push ax
|
|
||||||
|
|
||||||
; set the video mode correctly
|
|
||||||
xor ah, ah ; bios-function (set video mode)
|
|
||||||
mov al, 3 ; video mode with 80x25 characters
|
|
||||||
int 0x10
|
|
||||||
|
|
||||||
.epilog:
|
|
||||||
pop ax
|
|
||||||
ret
|
|
||||||
|
|
||||||
; cx: len_buffer
|
; cx: len_buffer
|
||||||
; si: buffer
|
; si: buffer
|
||||||
; dl: start_x NOTE: Will contain the end of the text on-screen
|
; dl: start_x NOTE: Will contain the end of the text on-screen
|
||||||
|
@ -82,189 +64,3 @@ print_string:
|
||||||
pop di
|
pop di
|
||||||
|
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
; ds: source
|
|
||||||
; es: destination
|
|
||||||
; dx: len_to_copy
|
|
||||||
mem_copy:
|
|
||||||
push ax
|
|
||||||
push bx
|
|
||||||
push cx
|
|
||||||
push dx
|
|
||||||
xor cx, cx
|
|
||||||
mov di, dx
|
|
||||||
|
|
||||||
; ax: first region's value
|
|
||||||
; bx: address at index
|
|
||||||
; cx: byte_index
|
|
||||||
; dx: second region's value
|
|
||||||
.loop:
|
|
||||||
|
|
||||||
; retrieve the source byte at the current index
|
|
||||||
mov bx, cx
|
|
||||||
mov dx, [bx]
|
|
||||||
|
|
||||||
; swap the two segments
|
|
||||||
; here, ax and bx are used differently.
|
|
||||||
mov ax, es
|
|
||||||
mov bx, ds
|
|
||||||
mov ds, ax
|
|
||||||
mov es, bx
|
|
||||||
|
|
||||||
mov [bx], cx
|
|
||||||
|
|
||||||
; swap the two segments back
|
|
||||||
mov ax, es
|
|
||||||
mov bx, ds
|
|
||||||
mov ds, ax
|
|
||||||
mov es, bx
|
|
||||||
|
|
||||||
inc cx
|
|
||||||
|
|
||||||
; if the end has NOT been reached
|
|
||||||
cmp cx, di
|
|
||||||
jb .loop ; => continue
|
|
||||||
|
|
||||||
ret
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
; ds: region_1
|
|
||||||
; es: region_2
|
|
||||||
; dx: len_regions
|
|
||||||
; ax: result
|
|
||||||
mem_equals:
|
|
||||||
|
|
||||||
push bx
|
|
||||||
push cx
|
|
||||||
push dx
|
|
||||||
push di
|
|
||||||
xor cx, cx
|
|
||||||
mov di, dx
|
|
||||||
|
|
||||||
; ax: first region's value
|
|
||||||
; bx: address at index
|
|
||||||
; cx: byte_index
|
|
||||||
; dx: second region's value
|
|
||||||
.loop:
|
|
||||||
mov bx, cx
|
|
||||||
mov dx, [bx]
|
|
||||||
|
|
||||||
; swap the two segments
|
|
||||||
; here, ax and bx are used differently.
|
|
||||||
mov ax, es
|
|
||||||
mov bx, ds
|
|
||||||
mov ds, ax
|
|
||||||
mov es, bx
|
|
||||||
|
|
||||||
mov bx, cx
|
|
||||||
mov ax, [bx]
|
|
||||||
|
|
||||||
cmp ax, dx
|
|
||||||
|
|
||||||
; swap the two segments back
|
|
||||||
mov ax, es
|
|
||||||
mov bx, ds
|
|
||||||
mov ds, ax
|
|
||||||
mov es, bx
|
|
||||||
|
|
||||||
; jump accordingly to the comparison above
|
|
||||||
jne .not_equal
|
|
||||||
inc cx
|
|
||||||
|
|
||||||
cmp cx, di ; if the loop needs to continue
|
|
||||||
jb .loop ; (if the end is NOT reached yet)
|
|
||||||
|
|
||||||
; it goes here if the two regions are completely equal
|
|
||||||
pop di
|
|
||||||
pop dx
|
|
||||||
pop cx
|
|
||||||
pop bx
|
|
||||||
|
|
||||||
mov ax, 1
|
|
||||||
ret
|
|
||||||
|
|
||||||
.not_equal:
|
|
||||||
|
|
||||||
pop di
|
|
||||||
pop dx
|
|
||||||
pop cx
|
|
||||||
pop bx
|
|
||||||
|
|
||||||
mov ax, 0
|
|
||||||
ret
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
; ax: start_sector (upper)
|
|
||||||
; bl: start_sector (lower)
|
|
||||||
; cx: wanted_sector_count
|
|
||||||
; dh: drive
|
|
||||||
; si: buffer_address (upper)
|
|
||||||
; di: buffer_address (lower)
|
|
||||||
load_sectors:
|
|
||||||
|
|
||||||
; INNER WORKING:
|
|
||||||
;
|
|
||||||
; This function first checks if it needs to load sectors going over
|
|
||||||
; a cylinder boundary and loads to that cylinder boundary first continuing
|
|
||||||
; in 63-sector - parts after that until the last, < 63-sectors part is read.
|
|
||||||
;
|
|
||||||
; This is done because some systems don't like reading over the boundaries
|
|
||||||
; between the cylinders.
|
|
||||||
;
|
|
||||||
; The actual part loading the data is in .read_from_disk, it's no different
|
|
||||||
; from the first read to the other reads. It just gets different arguments
|
|
||||||
; from 1).load_first_sectors, and 2) .load_further_sectors
|
|
||||||
;
|
|
||||||
|
|
||||||
.prolog:
|
|
||||||
push ax
|
|
||||||
push bx
|
|
||||||
push cx
|
|
||||||
push dx
|
|
||||||
push si
|
|
||||||
xor si, si ; current sector index
|
|
||||||
|
|
||||||
.load_first_sectors:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
; ah: EMPTY
|
|
||||||
; al: num_sectors_to_read
|
|
||||||
; bx: EMPTY
|
|
||||||
; ch: cylinder
|
|
||||||
; cl: sector
|
|
||||||
; dh: head
|
|
||||||
; dl: drive
|
|
||||||
.read_from_disk:
|
|
||||||
|
|
||||||
; set es:bx to the address in bytes to which the output should be written.
|
|
||||||
|
|
||||||
mov bx, si
|
|
||||||
shr bx, 9
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
mov ah, 0x02
|
|
||||||
int 0x13
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.load_further_sectors:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.epilog:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue