Bona Fide OS Developer
View unanswered posts | View active topics It is currently Thu Mar 28, 2024 11:39 am



Post new topic Reply to topic  [ 5 posts ] 
 There's something wrong with my boot code 
Author Message

Joined: Sat Sep 26, 2009 4:29 am
Posts: 15
Location: Philippines
Post There's something wrong with my boot code
School vacation, a great time to continue my osdev!! So I'll bother you
again with questions and bugs =). There's something wrong with int 0x13 fn 42 in my boot code.
Loading sectors was fine until loading the cluster chain on fat(see sys_loadclusterchain16).
At first, fat[0] or sector 4 is loaded w/o errors, but when it comes the need to load another
fat sector (fat[1]) an error occurs. I used bochsdbg in viewing mem & regs during execution.
I tried to check the DAP (@0x7c3e) before I loaded fat[1] (sector 5) using int 0x13 and it seemed to be ok.
DAP was also pointed correctly by ds:si. But error ocurred ( Error 01..what does it mean? My BIOS Enhanced
Disk Drive Specification Version 3.0 Rev 0.8 March 12, 1998... doesn't show me what it is).
Never mind the unused functions and tables. I will use it in future development.
Also SAJIAXXX.BIN is my second stage bootloader w/c is responsible for searching my OS in any drives
installed in your pc. But it is not yet finished. I'll continue making it after this problem solved.
Here's my code:
Code:
bits    16
org 0x7c00
jmp short boot
nop
SystemVersion      db "SAJIA OS"
BytesPerSector      dw 0x200
SectorsPerCluster   db 2
ReservedSector      dw 4
FATCount         db 2
RootDirCount      dw 0x40
SectorCount16      dw 19728 ;19728 19642 for data(9821 cluster)
MediaDescriptor      db 0xF8
FATsz16            dw 40    ; 10240 clusters
SectorsPerTrack      dw 63
HeadCount         dw 1
HiddenSectorCount   dd 0
SectorCount32      dd 0

;FAT16
FAT16_Drive            db 0x80
FAT16_FlagNT         db 0
FAT16_Signature         db 0x29
FAT16_SerialNumber      dd 0x01020304
FAT16_VolumeLabel      db "NO NAME    "
FAT16_SystemID         db "FAT16   "

;FAT32
FATsz32               equ 0x200+36
FAT32_extflg         equ   0x200+40
FAT32_fsver            equ 0x200+42
FAT32_rootcluster      equ 0x200+44
FAT32_fsinfo         equ 0x200+48
FAT32_ReservedSector   equ 0x200+50
FAT32_drv            equ 0x200+64
FAT32_Signature         equ 0x200+66
FAT32_SerialNumber      equ 0x200+67
FAT32_VolumeLabel      equ 0x200+71
FAT32_SystemID         equ 0x200+82
;DAP
dap:
db 0x10
db 0
dap_count db 0
db 0
dap_bfr dd 0
dap_lba dq 0
;PT

;datax
datax equ 0x100

;Drive
drv       equ datax         ;byte

;FS param
fs_rdirsect equ drv + 1      ;byte

;divx data
divx_nbits   equ fs_rdirsect+1      ;byte   nbits
divx_d      equ divx_nbits+1      ;dword   d
divx_t      equ divx_d+4         ;dword   t
divx_i      equ divx_t+4         ;byte   i

;check_fs data
fs_totalsector         equ divx_i+1         ;dword
fs_datastart         equ fs_totalsector+4   ;dword, relative to fs_vrbase
fs_fatsize            equ   fs_datastart+4      ;dword,
fs_vrbase            equ fs_fatsize+4      ;qword
fs_fatentsz            equ fs_vrbase+8         ;dword
fs_SectorsPerCluster   equ fs_fatentsz+4      ;dword
fs_dirstart            equ fs_SectorsPerCluster+4;dword relative to fs_vrbase
fs_bytespersector      equ fs_dirstart+4      ;dword
fs_rsvdsector         equ fs_bytespersector+4   ;dword
fs_bpc               equ fs_rsvdsector+4      ;dword
;sys_loadclusterchain data
lcc_latestsectloaded   equ fs_bpc+4         ;dword
lcc_entrypersector      equ lcc_latestsectloaded+4;dword
sectortmp       equ 0x200
datax2         equ 0x400
datax3         equ 0x8400

boot:
   cli
   xor ax,ax
   mov ds,ax
   mov es,ax
   ;mov ax,0x9FE0 ;
   mov ss,ax
   mov sp,0x7c00
   ;I'll make sure that stack doesn't meet the cluster chain
   
   ;mov sp,0x200   ;stack top @ 0xA0000
   
   mov si,msg_load
   call sys_print
   
   mov [drv],dl
   call sys_int13ext
   
   mov al,3      ; Load 3 sectors
   mov edi,0x7e00   ; to 0x7e00
   mov ebx,0x0001   ; from lba 1
   xor edx,edx      ;
   call sys_read
   
   mov edi,sectortmp
   xor ebx,ebx
   xor edx,edx
   mov al,1
   call sys_read
   xor eax,eax
   mov [fs_vrbase],eax
   mov [fs_vrbase+4],eax
   call sys_checkfs
   ;return ax, 1->fat16, 2->fat32
   cmp ax,1
   je sys_loadfat16root
   jmp sys_unfs

sys_read:
   push esi
   push ecx
   ;edi - buffer address [segment:offset; 16:16]
   ;ebx - lower dword lba, al-number of sectors
   ;edx - upper dword lba, cf - on error
   mov cx,03
   sys_read_retry:
   mov byte[dap_count],al
   mov dword[dap_bfr],edi
   mov dword[dap_lba+4],edx
   mov dword[dap_lba],ebx
   mov ah,0x42
   pusha         ;makes the parameters unhurt
   mov dl,[drv]
   mov esi,dap
   int 0x13
   popa
   jnc sys_read_loadok
   loop sys_read_retry
   sys_read_loadok:
   pop ecx
   pop esi
ret

sys_int13ext:
   ; check if int 0x13 ext is present
   mov ah,0x41
   mov bx,0x55aa
   int 0x13   
   jnc int13extok
   mov si,msg_noint13ext
   call sys_print
   jmp $
   int13extok:
ret

divx:
; memory efficient than a macro (btw how can I make a macro on nasm?)

; eax-dividend
; ebx-divisor
; eax-qoutient
; edx-remainder
   xor edx,edx
   cmp ebx,0
   je divxz
   cmp eax,0
   je divxz
   div ebx
   ret
   divxz:
   xor edx,edx
   xor eax,eax
ret

sys_findstr:
;0:si - data start search offset address
;0:di - data to be searched offset address
;cx   - length of data to be searched
;bx   - data limit (bx+si) size
; found cl=1
   push bp
   push ax
   add bx,si
   mov bp,di
   mov ax,cx
   cmpnext:
   push ax
   mov al,[si]
   cmp al,[bp]
   pop ax
   je nextchar
   inc si
   mov ax,cx
   mov bp,di
   cmp si,bx
   jb cmpnext
   jmp strnotfound
   nextchar:
   inc si
   inc bp
   dec ax
   cmp ax,0
   je strfound
   jmp cmpnext
   strnotfound:
   xor cx,cx
   jmp strxt
   strfound:
   sub si,cx
   mov cl,1
   strxt:
   pop ax
   pop bp
ret

sys_print:
   ;si - msg address
   pusha
   cld
   mov ah, 0x0e      ; int 0x10 teletype function
   sys_printx:
   lodsb            ; Get char from string
   cmp al, 0
   je sys_printxt      ; If char is zero, end of string
   int 0x10         ; Otherwise, print it
   jmp short sys_printx
   sys_printxt:
   popa
ret

sys_printint:
   ;eax-int to print
   push cx
   mov cl,32
   prntint_cnv:
   push eax
   sub cl,4
   shr eax,cl
   and al,0xF
   cmp al,9
   ja prntintx1
   add al,0x30
   jmp prntintx2
   prntintx1:
   add al,0x37
   prntintx2:
   mov ah,0x0e
   int 0x10
   cmp cl,0
   pop eax
   jne prntint_cnv
   
   mov ax,0x0e20
   int 0x10
   
   pop cx
ret

sys_unfs:
mov si,msg_unfs
call sys_print
trap:;0x7d7c
mov si,msg_trap
call sys_print
jmp $
;7d85
msg_load       db "...",0x0a,0x0d,0
msg_trap      db "X",0
msg_noint13ext   db "Ur systm s 2 old",0
msg_unfs      db "Unsupported FS!",0x0a,0x0d,0
msg_liner      db 0x0a,0x0d,0

times 446-($-$$) db 0   ;
;PTentry
partbase:
db 0x80 ; active marker
db 0   ;start C
db 0   ;H
db 0   ;S
db 0   ;part type
db 0   ;end C
db 0   ;H
db 0   ;S
dd 0x00000000 ; Start lba
dd 0x00000000 ; size
;----------------------------
db 0x80 ; active marker
db 0   ;start C
db 0   ;H
db 0   ;S
db 0   ;part type
db 0   ;end C
db 0   ;H
db 0   ;S
dd 0x00000000 ; Start lba
dd 0x00000000 ; size
;-----------------------------
db 0x80 ; active marker
db 0   ;start C
db 0   ;H
db 0   ;S
db 0   ;part type
db 0   ;end C
db 0   ;H
db 0   ;S
dd 0x00000000 ; Start lba
dd 0x00000000 ; size
;--------------------------------
db 0x80 ; active marker
db 0   ;start C
db 0   ;H
db 0   ;S
db 0   ;part type
db 0   ;end C
db 0   ;H
db 0   ;S
dd 0x00000000 ; Start lba
dd 0x00000000 ; size
dw 0xAA55      ;boot signature
sys_loadfat16root:
   xor edx,edx
   mov eax,[fs_datastart]   ;load (fs_datastart-fs_dirstart) sectors
   sub eax,[fs_dirstart]   ;from
   mov ebx,[fs_dirstart]   ;LBA fs_dirstart+fs_vrbase
   add ebx,[fs_vrbase]      ;
   adc edx,[fs_vrbase+4]   ;
   mov edi,datax3         ;to memory 0x8400
   ;edi - buffer address [segment:offset; 16:16]
   ;ebx - lower dword lba, al-number of sectors,edx - upper dword lba, cf - on error
   call sys_read
   jmp FindSaiOnRoot16
commonret:
   jmp $   ;until here for now
   call sys_eA20
   lgdt [GDTr]

   mov eax,cr0
   or eax,1
   mov cr0,eax
   
   mov ax,0x10
   mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    mov ss,ax

   mov esp,0xFFFF
   
   jmp 0x8:0x8400   ;[(index(13-bit),TI,RPL(2-bit)]:offset

sys_loadclusterchain16:
   ;eax-relative cluster offset
   pusha
   xor edi,edi   
   mov esi,datax2
   and eax,0xFFFF
   mov [esi],ax
   lcc_getnextcluster:
   mov ebx,[lcc_entrypersector]
   call divx
   mov ecx,eax
   mov edi,edx   
   shl edi,1
   
   add ecx,[fs_rsvdsector]
   
   cmp ecx,[lcc_latestsectloaded]
   je lcc_loadskip
   mov [lcc_latestsectloaded],ecx
   mov eax,1                  ;number of sector to load
   xor edx,edx                  ;LBA = fatsector+fatstart+vrbase
   mov ebx,ecx                  ;
   add ebx,[fs_vrbase]            ;
   adc edx,[fs_vrbase+4]         ;
   
   ;; cmp bx,5 ; I also tried this one
   ;; je xzxc   ; ---------->>
   
   push edi                  ;to memory
   mov edi,sectortmp            ;sectortmp=0x200
   ;edi - buffer address [segment:offset; 16:16];  ebx - lower dword lba, al-number of sectors,edx - upper dword lba, cf - on error
   call sys_read
   pop edi
   lcc_loadskip:
   add esi,2

   cmp word[sectortmp+di],0xFFFF
   je lcc_endofchain
   cmp word[sectortmp+di],0
   je lcc_endofchain
   cmp word[sectortmp],0
   
   mov ax,[sectortmp+di]
   mov [esi],ax
   and eax,0xFFFF
   jmp lcc_getnextcluster
   
   lcc_endofchain:
   jmp $         ;until here for now
   
   mov edi,datax2
   mov ecx,esi
   xor esi,esi
   mov ebp,datax3
   
   loadchain:
   push edi
   xor edx,edx                  ;LBA = (relative_sector-2)*spc
   movzx ebx,word[edi]            ;
   sub ebx,2
   
   mov eax,ebx
   mov ebx,[fs_SectorsPerCluster]
   mul bx
   mov ebx,eax
   
   add ebx,[fs_datastart]         ;LBA+=data_start+vrbase
   add ebx,[fs_vrbase]            ;
   adc edx,[fs_vrbase+4]         ;to memory
   mov eax,[fs_SectorsPerCluster]   ;number of sector to load
   
   push ebp
   mov di,bp                  ;
   and di,0xF                  ; (ebp>>4):bp&0xF
   shl ebp,12                  ;
   xor bp,bp                  ;
   or edi,ebp
   pop ebp
   
   push ecx
   ;edi - buffer address [segment:offset; 16:16];  ebx - lower dword lba, al-number of sectors,edx - upper dword lba, cf - on error
   call sys_read
   pop ecx
   pop edi
   
   add ebp,[fs_bpc]
   add edi,2
   cmp edi,ecx
   jbe loadchain
   
   popa
ret
;; xzxc: ;<<-----------------
;;   ;The I tried to use interupt
;;   mov ax,0x0eeb      ;@ 0x7ee6
;;   int 0x10
;;   ;Yeah it returned!!
;;   ;But nothing happened...it is supposed to display tadpole like character.
;;   ;I thought that I smashed the data area for BIOS..but according to
;;  ;the memory map I had, addresses 0x100 to 0xA0000 is a free memory to use,
;;   ;Isn't it?? I'm realy confused now...... TT
;;   jmp $

sys_checkfs:
;check fs type of a bootsector loaded at "sectortmp"
;return ax, 1->fat16, 2->fat32
   push ebx
   push ecx
   push edx
   ;check rsvd sector size
   
   cmp word[ReservedSector-0x7a00],0
   je rsvdsctr32
   movzx edx,word[ReservedSector-0x7a00]
   mov [fs_rsvdsector],edx
   jmp rsvdsctrx
   rsvdsctr32:
   mov edx,[FAT32_ReservedSector]
   mov [fs_rsvdsector],edx
   rsvdsctrx:
   
   ;check sector count
   cmp word[SectorCount16-0x7a00],0      ;
   jne sectsz16
   mov edx,[SectorCount32-0x7a00]         ;
   mov dword[fs_totalsector],edx
   jmp sectszx
   sectsz16:
   movzx edx,word[SectorCount16-0x7a00]   ;
   mov dword[fs_totalsector],edx
   sectszx:
   
   cmp word[FATsz16-0x7a00],0
   jne FATsz16x
   mov eax,dword[FATsz32-0x7a00]
   mov [fs_fatsize],eax
   jmp FATszx
   FATsz16x:
   movzx eax,word[FATsz16-0x7a00]
   mov [fs_fatsize],eax
   FATszx:
   
   movzx bx,byte[FATCount-0x7a00]
   push edx
   mul bx
   pop edx   ;discard edx in edx:eax
   
   push eax
   movzx eax,word[RootDirCount-0x7a00]      ;
   shl eax,5
   movzx ebx,word[BytesPerSector-0x7a00]   ;
   mov [fs_bytespersector],ebx
   push edx
   call divx   ;eax=((rootdircount*32)/bps)=rootdircount
   cmp edx,0   ;ceiling
   je rx1
   inc eax
   rx1:
   pop edx
   mov ecx,eax
   pop eax
   
   push edx
   mov ebx,[fs_rsvdsector]
   mov [fs_datastart],ebx       ; fs_datastart=ReservedSector
   add [fs_datastart],eax      ; fs_datastart+=(fatsz*FATCount)
   mov edx,[fs_datastart]
   mov [fs_dirstart],edx
   add [fs_datastart],ecx      ; fs_datastart+=rootdirsectors
   pop edx
   
   sub edx,ebx ; totalsector-=ReservedSector
   sub edx,eax   ; totalsector-=fatsz*FATCount
   sub edx,ecx ; totalsector-=rootdirsectors
   mov eax,edx
      
   movzx ebx,byte[SectorsPerCluster-0x7a00]
   mov [fs_SectorsPerCluster],ebx
   ;eax is NumberOfDataSector
   call divx   ;eax=ebx/eax=NumberOfDataSector/SectorsPerCluster = number of data cluster
   ;floor
   
   cmp eax,4085
   jb sys_checkfs_un
   cmp eax,65525
   jb sys_checkfs_fat16
   cmp eax,268435445
   jb sys_checkfs_fat32
   
   sys_checkfs_un:
   mov ax,0
   jmp sys_checkfsx
   
   sys_checkfs_fat16:
   mov dword[fs_fatentsz],2
   mov ax,1
   jmp sys_checkfsx
   
   sys_checkfs_fat32:
   mov dword[fs_fatentsz],4
   mov ax,2
   jmp sys_checkfsx
   
   sys_checkfsx:
   
   push eax
   mov eax,[fs_bytespersector]
   mov ebx,[fs_fatentsz]
   call divx
   mov [lcc_entrypersector],eax

   mov eax,[fs_bytespersector]
   mov ebx,[fs_SectorsPerCluster]
   mul bx
   mov [fs_bpc],eax
   pop eax
   
   pop edx
   pop ecx
   pop ebx
ret

FindSaiOnRoot16:
   mov si,datax3
   findrep:
   mov di,filename
   mov cx,11
   mov bx,0x7BFF
   call sys_findstr
   ;0:si   - data start search offset address
   ;0:di   - data to be searched offset address
   ;cx   - lenght of data to be searched
   ;bx   - data limit (bx+si)
   ; found cl=1
   cmp cl,0
   je FindSaiOnRoot_notfound
   push esi
   movzx eax,si
   xor ebx,ebx
   mov bl,0x20
   ; eax-dividend
   ; ebx-divisor
   ; eax-qoutient
   ; edx-remainder
   call divx
   pop esi
   cmp dl,0
   jne findrep
   
   add si,0x1a
   
   xor eax,eax
   mov ax,[si]
   call sys_loadclusterchain16
   jmp commonret
   FindSaiOnRoot_notfound:
   mov si,nofile
   call sys_print
   jmp $

sys_dumpmem:
   ;si-dumpstart
   ;cl-dump size in byte*4
   pusha
   xor edx,edx
   dumpx:
   push cx
   mov cx,24
   dumpxx:
   lodsb            ; Get char from string
   shl eax,cl
   or edx,eax
   xor eax,eax
   sub cx,7
   loop dumpxx
   pop cx
   lodsb
   or edx,eax
   mov eax,edx
   xor edx,edx
   call sys_printint
   loop dumpx
   popa
ret

sys_eA20:
   pusha
   mov cx,5
   ea20_1:
   ea20_cmdw1:
   xor ax,ax
   in al,0x64
   bt ax,1
   jc ea20_cmdw1
   mov al,0x0D0
   out 0x64,al
   ; Wait for the controller to be ready with a byte of data
   ea20_dw1:
   xor ax,ax
   in al,0x64
   bt ax,0
   jnc ea20_dw1
   ; Read the current port status from port 60h
   xor ax,ax
   in al,0x60
   push ax
   ;Wait for the controller to be ready for a command
   ea20_cmdw2:
   in al,0x64
   bt bx,1
   jc ea20_cmdw2
   ; Tell the controller we want to write the status byte again
   mov al,0x0D1
   out 0x64,al
   ;Wait for the controller to be ready for the data
   ea20_cmdw3:
   xor ax,ax
   in al, 64h
   bt ax,1
   jc ea20_cmdw3
   pop ax
   ; Turn on the A20 enable bit
   or al,2
   out 0x60,al
   
   ;; Wait for the controller to be ready for a command
   ea20_cmdw4:
   xor ax,ax
   in al,0x64
   bt bx,1
   jc ea20_cmdw4
   ; Send the command D0h: read output port.
   mov al,0x0D0
   out 0x64,al   
   ; Wait for the controller to be ready with a byte of data
   ea20_dw2:
   xor ax,ax
   in al,0x64
   bt ax,0
   jnc ea20_dw2
   ; Read the current port status from port 60h
   xor ax,ax
   in al,0x60
   ; Is A20 enabled?
   bt ax,1
   ; If carry is on, A20 is on.
   jc ea20_ok
   loop ea20_1
   
   ;Other method
   mov cx,5
   ea20_2:
   ; Wait for the keyboard to be ready
   ea20_cmdw5:
   xor ax,ax
   in al,0x64
   bt ax, 1
   jc ea20_cmdw5

   ; turn on A20
   mov al,0x0DF
   out 0x64,al
   ; Wait for the controller to be ready for a command
   ea20_cmdw6:
   xor ax,ax
   in al,0x64
   bt ax,1
   jc ea20_cmdw6
   mov al,0x0D0
   out 0x64,al
   ; Wait for the controller to be ready with a byte of data
   ea20_dw3:
   xor ax,ax
   in al,0x64
   bt ax,0
   jnc ea20_dw3
   ; Read the current port status from port 60h
   xor ax,ax
   in al,0x60
   ; Is A20 enabled?
   bt ax, 1
   jc ea20_ok
   loop ea20_2
   mov si,noa20
   call sys_print
   jmp $
   ea20_ok:
   sti
   popa
ret

filename      db "SAJIAXXXBIN",0
nofile         db "Boot file not found. You deleted it!!",0
noa20         db "Unable to enable A20 gate. Booting cannot be continued.",0

GDTr:
dw GDTe-GDT-1   ;Limit
dd GDT      ;Base
GDT:
dq 0 ;Dummy entry
CODESEL:
dw 0xFFFF;Lower limit
dw 0;Lower base
db 0;Mid base
db 0x9A;p,dpl(2-bit),s,type(4-bit)
db 0xCF;G,D/B,0,avl.Upper limit(4-bit)
db 0;Upper base
DATASEL:
dw 0xFFFF;Lower limit   
dw 0;Lower base
db 0;Mid base
db 0x92;p,dpl(2-bit),s,type(4-bit)
db 0xCF;G,D/B,0,avl.Upper limit(4-bit)
db 0;Upper base
GDTe:
times 2048 -($-$$) db 0   ;


Fri Apr 09, 2010 12:24 am
Profile

Joined: Tue Jan 19, 2010 11:51 pm
Posts: 66
Post Re: There's something wrong with my boot code
Looks like you have a clue, unlike some of the people around here. I picked my company motto, "Happy programmers make us happy," not that my company has anything to do with this. You make me happy. You'll figure-it out. Too much code for me to look at. :)


Fri Apr 09, 2010 2:10 am
Profile

Joined: Mon Oct 26, 2009 2:33 pm
Posts: 38
Location: United Kingdom
Post Re: There's something wrong with my boot code
@Terry: This website is a learning resource, the point is people come here to learn and help. So you can't expect everyone to suddenly be amazing; as you seem to think you are. On the subject of helping, if you're not going to bother helping the OP, why bother posting?

@prinzrainer: Can you strip out as much of the superfluous code as possible and replicate the problem with a little code as possible, that will help you narrow down exactly where the problem lies and can help us move forward and get it solved.


Fri Apr 09, 2010 6:28 am
Profile

Joined: Tue Jan 19, 2010 11:51 pm
Posts: 66
Post Re: There's something wrong with my boot code
@AndyEsser: Some people on this site think they deserve an award for locating where an unwanted infinite loop is happening! Clueless--lazy and no perseverence. I wrote a big comment on perseverence when it comes to hardware code. You have to not be lazy. This guy wrote code to dump blocks. I was impressed. He has lots of diagnostic code. There is hope for him. When you get stuck, try something, no matter how crazy -- you might learn something. You have to not be lazy and willing put-in diagnostics. I'm confident there's hope and he can figure-it out himself. I wrote mine with no forum help.

@prinzrainer: I glanced at your code. I saw you put the stack pointer at 7C00. I don't know what's below that, but I'd be nervous of hitting something the BIOS was using. That might result in a screwy BIOS. Maybe, it's safe, I don't know.


Fri Apr 09, 2010 12:10 pm
Profile

Joined: Sat Sep 26, 2009 4:29 am
Posts: 15
Location: Philippines
Post Re: There's something wrong with my boot code
I already solved it by putting all my data above 0x7c00...thanks anyway


Sat Apr 10, 2010 4:23 am
Profile
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 5 posts ] 


Who is online

Users browsing this forum: No registered users and 23 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Designed by Vjacheslav Trushkin and tweaked by the BF Team.