flat assembler
Message board for the users of flat assembler.

Index > Main > Help with converting Delphi code. [beleberda detected]

Author
Thread Post new topic Reply to topic
SeryZone



Joined: 20 Dec 2013
Posts: 38
Location: Ukraine, Kryviy Rih
SeryZone
Hello! More than half year I try to understand assembler. Now I try to rewrite another formulas from my program.
So, I calculate index on Delphi as:

Code:
{$L Functions.obj}
procedure ModifyIndex(min:integer; dat1:integer; dat2:single; step:single; out Index:integer); stdcall; external;    


Code:
ColorIndex := round( 4096 * sqrt(log2((Iterdat[x*maxy+y]+1-SmD[x*maxy+y])/min)/step));    

I tried to rewrite - ColorIndex is 2 for all occasions! Here:
Code:
format MS COFF

include "Macro/Proc32.Inc"

section ".code" executable
public  ModifyIndex

C1 dd 1.0
C4096 dd 4096.0

proc ModifyIndex, min, dat1, dat2, step, Index
;Index := round( 4096 * sqrt(log2((dat1+1-dat2)/min)/step));
finit
fld [C4096];st5
fld [min]  ;st4
fld [dat1] ;st3
fld [dat2] ;st2
fld [step] ;st1
fld1       ;st0

fadd st0, st3
fsub st0, st2
fyl2x
fdiv st0, st4
fsqrt
fdiv st0, st1
fmul st0, st5
fstp [Index]

ret
endp    


Please, tell me, what I did wrong???
p.s. please, don't say that I noob, newbie or other, I have low self-grade, I won't upset...
Post 25 Apr 2014, 17:28
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1412
Location: Toronto, Canada
AsmGuru62
I think you first take FYL2X and then divide by MIN, but your formula says 1st divide by min and THEN take FYL2X.
Post 25 Apr 2014, 18:52
View user's profile Send private message Send e-mail Reply with quote
SeryZone



Joined: 20 Dec 2013
Posts: 38
Location: Ukraine, Kryviy Rih
SeryZone
AsmGuru62 wrote:
I think you first take FYL2X and then divide by MIN, but your formula says 1st divide by min and THEN take FYL2X.


Okay, I correct:
Code:
format MS COFF

include "Macro/Proc32.Inc"

section ".code" executable
public  ModifyIndex

C1 dd 1.0
C4096 dd 4096.0

proc ModifyIndex, min, dat1, dat2, step, Index
;Index := round( 4096 * sqrt(log2( (dat1+1-dat2)/min )/step));
finit
fld [C4096];st5
fld [min]  ;st4
fld [dat1] ;st3
fld [dat2] ;st2
fld [step] ;st1
fld1       ;st0

fadd st0, st3
fsub st0, st2
fdiv st0, st4
fyl2x
fsqrt
fdiv st0, st1
fmul st0, st5
frndint
fstp [Index]

ret
endp    


and result stay as [2]
Post 25 Apr 2014, 19:06
View user's profile Send private message Reply with quote
SeryZone



Joined: 20 Dec 2013
Posts: 38
Location: Ukraine, Kryviy Rih
SeryZone
Where I got mistake??? please, tell!
Post 25 Apr 2014, 20:54
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1412
Location: Toronto, Canada
AsmGuru62
You have to check if parameters are passed/returned properly.

dat1, min are integer values, so they cannot be loaded with FLD, but with FILD.

I suggest putting a breakpoint at the beginning (int3 opcode) and checking what debugger says and track how parameters are used.

P.S. Delphi has a nice optimizer, so you may not get better code making the routine in FASM and then linking it with your program.

Maybe some improvement needed in Delphi code itself?
Like, here:

round(4096* ...);

better would be:

4096*round(...)

Also, MUL by 4096 most likely optimized by Delphi as a shift, and in the FASM code you use FMUL, which is way slower.

Another improvement: how many times that function called? probably millions of times... try to pass parameters inside of a structure, then the call will push a single parameter instead of 5 on each iteration.
Post 25 Apr 2014, 21:16
View user's profile Send private message Send e-mail Reply with quote
SeryZone



Joined: 20 Dec 2013
Posts: 38
Location: Ukraine, Kryviy Rih
SeryZone
Code:
      begin
        index := 0;
        step := log2(max/min);
          for y := 0 to maxy-1 do
            for x := 0 to maxx-1 do
          begin
            if (IterDat[x*maxy+y] >= max) then
            begin
              buffer[index] := 0; // red
              buffer[index + 1] := 0; // green
              buffer[index + 2] := 0; // blue
            end
            else if (IterDat[x*maxy+y] > 0) then
            begin
              ColorIndex := round( 4096 * (log2((Iterdat[x*maxy+y]+1-SmD[x*maxy+y])/min)/step) ) mod 4096;
              begin
              buffer[index] := pal[ColorIndex].b; // red
              buffer[index + 1] := pal[ColorIndex].g; // green
              buffer[index + 2] := pal[ColorIndex].r; // blue
              end;
            end;
            inc(index, 4)
          end;
      end;    


Here all code.
4096*round() I can't make because all values in round() between 0 and 1.
And one. if on SSE or AVX i can calculate 4 or 8 sqrt() functions, but logarithm - only on FPU...
Post 26 Apr 2014, 07:09
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1412
Location: Toronto, Canada
AsmGuru62
Yes, I just realized that it is not the same thing (I mean 4096).
I still suggest to put a breakpoint and examine what happens in the function in debugger.

What are the types of your arrays?

Iterdat
SmD
pal[x].r - is it BYTE?

And other vars:

min, max - are those integers?
Code:
;              buffer[index] := pal[ColorIndex].b; // red
;              buffer[index + 1] := pal[ColorIndex].g; // green
;              buffer[index + 2] := pal[ColorIndex].r; // blue
    

Why .r commented as "// blue"?
Is the comment wrong or the code wrong?
Post 26 Apr 2014, 12:33
View user's profile Send private message Send e-mail Reply with quote
SeryZone



Joined: 20 Dec 2013
Posts: 38
Location: Ukraine, Kryviy Rih
SeryZone
AsmGuru62 wrote:
Yes, I just realized that it is not the same thing (I mean 4096).
I still suggest to put a breakpoint and examine what happens in the function in debugger.

What are the types of your arrays?

Iterdat
SmD
pal[x].r - is it BYTE?

And other vars:

min, max - are those integers?
Code:
;              buffer[index] := pal[ColorIndex].b; // red
;              buffer[index + 1] := pal[ColorIndex].g; // green
;              buffer[index + 2] := pal[ColorIndex].r; // blue
    

Why .r commented as "// blue"?
Is the comment wrong or the code wrong?


No, in delphi bgr, not rgb (I don't know, why).

min, max - integers,but in improved code it single precision.
Yes, pal[x]=(r:byte, g:byte, b:byte)
Post 26 Apr 2014, 13:08
View user's profile Send private message Reply with quote
SeryZone



Joined: 20 Dec 2013
Posts: 38
Location: Ukraine, Kryviy Rih
SeryZone
I tried to rewrite simple linear visualization, other types (sqrt, log2) I didn't write.
Code:
format MS COFF

include "Macro/Proc32.Inc"

section ".code" executable
public Linear
public SquareRoot
public Logarithmic

C4096           dd 4096.0
DC              rd 1
buffer          dd ?
step            rd 1
ColorIndex      rd 1
pal             rb 4096*3
;--------------------------------------------------------------------------------------------------;
proc Linear bmp, data, width, height, min, max, palette
;bmp:TBitMap; data:array[0..Width*Heigth-1]of single, width, height:integer; min, max:single; palette: array of (byte, byte, byte);
mov [width],eax                 ;eax = width
imul [height]                   ;eax*height
shl eax, 2                      ;eax*4
invoke HeapAlloc,[buffer],HEAP_ZERO_MEMORY,eax  ;make buffer size as eax elements

finit
fild [mim]     ;st1, after st5
fild [max]     ;st0, after st4
fsub st0, st1  ;step = max-min
fst [step]    ;store to memory

mov [palette],edx  ;load palette
mov edx, [pal]     ;12288 bytes

mov [height], edi      ;Height
mov eax, edi
dec edi

mov ecx, 0
fory:      ;counter is edi
mov [width], edx
dec edx
        forx:  ;counter is edx
            fild [step]            ;st3
            fild [C4096]           ;st2
            fild [data+ecx*eax+edi];st1
            fldz                   ;st0 = 0
            ;ColorIndex := round( 4096 * ((data[x*maxy+y]-min)/step) );
            fmov st0, st1       ;st0 = data
            fsub st0, st5       ;st0 = data-min
            fdiv st0, st3       ;st0 = (data-min)/step
            fmul st0, st2       ;st0 = 4096*((data-min)/step)
            frndint
            fstp [ColorIndex]
            mov [buffer+ecx], [pal*3+ColorIndex+3] ;Third index is blue
            mov [buffer+ecx], [pal*3+ColorIndex+2] ;Second index is green
            mov [buffer+ecx], [pal*3+ColorIndex+1] ;First index is red
            add ecx, 4

            dec edx
            cmp edx, 0
            jl forx
    dec edi
    cmp edi, 0
    jl fory

ret
endp
;--------------------------------------------------------------------------------------------------;
proc SquareRoot

ret
endp
;--------------------------------------------------------------------------------------------------;
proc Logarithmic

ret
endp     


Delphi original:
Code:
    2:           //linear
      begin
        index := 0;
        step := max-min;
          for y := 0 to maxy-1 do
            for x := 0 to maxx-1 do
          begin
            if (IterDat[x*maxy+y] >= max) then
            begin
              buffer[index] := 0; // red
              buffer[index + 1] := 0; // green
              buffer[index + 2] := 0; // blue
            end
            else if (IterDat[x*maxy+y] > 0) then
            begin
              ColorIndex := round( 4096 * ((Iterdat[x*maxy+y]-min)/step) );
              begin
              buffer[index] := pal[ColorIndex].b; // red
              buffer[index + 1] := pal[ColorIndex].g; // green
              buffer[index + 2] := pal[ColorIndex].r; // blue
              end;
            end;
            inc(index, 4)
          end;
      end;
SetDIBitsToDevice(DC, 0, 0, Maxx, Maxy,0,0,0, maxy, @Buffer[0], MyBMInfo, DIB_RGB_COLORS)
    


So, help me, I know, that I got mistakes! =(
Post 26 Apr 2014, 13:24
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1412
Location: Toronto, Canada
AsmGuru62
Here is some code (the whole thing):
Code:
; ---------------------------------------------------------------------------
; FILE: MakeImage.Asm
; DATE: April 26, 2014
; ---------------------------------------------------------------------------
struct PalItem
    red    db ?
    green  db ?
    blue   db ?
ends
; ---------------------------------------------------------------------------
struct IParams
    ;
    ; Fill these members (in Delphi code) BEFORE calling MakeImage_Entry
    ;
    max      dd ?  ; integer
    min      dd ?  ; integer
    maxx     dd ?  ; integer
    maxy     dd ?  ; integer
    IterDat  dd ?  ; pointer to array of integers
    SmD      dd ?  ; pointer to array of integers
    buffer   dd ?  ; pointer to array of bytes (or PalItem structures?)
    pal      dd ?  ; pointer to array of PalItem structures
    ;
    ; These are used inside MakeImage_Entry as locals
    ;
    _4096    dd ?
    index    dd ?
    x        dd ?
    y        dd ?
    step     dq ?  ; double
ends

align 32
MakeImage_Entry:
; ---------------------------------------------------------------------------
; INPUT (stdcall):
;   Pointer to IParams instance
; ---------------------------------------------------------------------------
    push      ebp
    mov       ebp, esp
    push      ebx esi edi
    mov       ebp, [ebp + 8]    ; EBP --> IParams structure

    xor       eax, eax
    mov       [ebp + IParams.index], eax   ; index := 0
    mov       [ebp + IParams.y], eax       ; y     := 0

    mov       [ebp + IParams._4096], 4096
    mov       edi, [ebp + IParams.buffer]
    finit
    ;
    ; step := log2 (max/min)
    ;
    fld1
    fild      [ebp + IParams.max]
    fidiv     [ebp + IParams.min]
    fyl2x
    fstp      [ebp + IParams.step]
    ;
    ; for y := 0 to maxy-1
    ;
.next_y:
    and       [ebp + IParams.x], 0      ; x := 0
    ;
    ; for x := 0 to maxx-1
    ;
align 16
.inner_loop:
    mov       esi, [ebp + IParams.IterDat]
    mov       ebx, [ebp + IParams.SmD]
    ;
    ; ECX = x*maxy+y
    ;
    mov       ecx, [ebp + IParams.x]
    imul      ecx, [ebp + IParams.maxy]
    add       ecx, [ebp + IParams.y]
    ;
    ; EAX = IterDat[ecx]
    ;
    mov       eax, [esi + ecx*4]
    xor       edx, edx
    ;
    ; if (IterDat[x*maxy+y] >= max) then set RGB=0 
    ;
    cmp       eax, [ebp + IParams.max]
    jge       .set_rgb_black
    ;
    ; else if (IterDat[x*maxy+y] > 0)
    ;
    cmp       eax, edx
    jng       .increase_x
    ;
    ; ColorIndex := round(4096 * (log2((Iterdat[x*maxy+y]+1-SmD[x*maxy+y])/min)/step)) mod 4096;
    ;
    mov       edx, [ebx + ecx*4]    ; EDX = SmD[ecx]
    ;
    ; EAX = Iterdat[x*maxy+y] + 1 - SmD[x*maxy+y]
    ;
    add       eax, 1
    sub       eax, edx
    ;
    ; log2 (EAX/min)
    ;
    fld1
    push      eax
    fild      dword [esp]
    fidiv     [ebp + IParams.min]
    fyl2x

    fdiv      [ebp + IParams.step]     ; /step
    fimul     [ebp + IParams._4096]    ; *4096
    fistp     dword [esp]              ; round (...)
    pop       eax
    ;
    ; EAX mod 4096
    ;
    mov       ecx, 4096
    xor       edx, edx
    div       ecx
    ;
    ; EDX is a ColorIndex
    ;
    lea       eax, [edx + edx*2]       ; EAX = 3*ColorIndex (PalItem is 3 bytes)
    mov       ebx, [ebp + IParams.pal]
    add       ebx, eax                 ; EBX points to pal[ColorIndex]
    ;
    ; buffer[index]     := pal[ColorIndex].b;
    ; buffer[index + 1] := pal[ColorIndex].g;
    ; buffer[index + 2] := pal[ColorIndex].r;
    ;
    mov       ecx, [ebp + IParams.index]

    mov       al, [ebx + PalItem.blue]
    mov       [edi + ecx], al

    mov       al, [ebx + PalItem.green]
    mov       [edi + ecx + 1], al

    mov       al, [ebx + PalItem.red]
    mov       [edi + ecx + 2], al
    jmp       .increase_x

.set_rgb_black:
    ;
    ; buffer[index]     := 0;
    ; buffer[index + 1] := 0;
    ; buffer[index + 2] := 0;
    ;
    mov       ecx, [ebp + IParams.index]
    mov       [edi + ecx], edx
    ;
    ; Here ^^^: since index moves by 4 byte steps - I just write 4 zero bytes into buffer
    ;

.increase_x:
    add       [ebp + IParams.index], 4
    add       [ebp + IParams.x], 1
    mov       ecx, [ebp + IParams.x]
    cmp       ecx, [ebp + IParams.maxx]
    jb        .inner_loop
    ;
    ; Increase y
    ;
    add       [ebp + IParams.y], 1
    mov       ecx, [ebp + IParams.y]
    cmp       ecx, [ebp + IParams.maxy]
    jb        .next_y

.exit:
    pop       edi esi ebx ebp
    ret       4
    
Post 26 Apr 2014, 14:39
View user's profile Send private message Send e-mail Reply with quote
SeryZone



Joined: 20 Dec 2013
Posts: 38
Location: Ukraine, Kryviy Rih
SeryZone
AsmGuru62 wrote:
Here is some code (the whole thing):
Code:
; ---------------------------------------------------------------------------
; FILE: MakeImage.Asm
; DATE: April 26, 2014
; ---------------------------------------------------------------------------
struct PalItem
    red    db ?
    green  db ?
    blue   db ?
ends
; ---------------------------------------------------------------------------
struct IParams
    ;
    ; Fill these members (in Delphi code) BEFORE calling MakeImage_Entry
    ;
    max      dd ?  ; integer
    min      dd ?  ; integer
    maxx     dd ?  ; integer
    maxy     dd ?  ; integer
    IterDat  dd ?  ; pointer to array of integers
    SmD      dd ?  ; pointer to array of integers
    buffer   dd ?  ; pointer to array of bytes (or PalItem structures?)
    pal      dd ?  ; pointer to array of PalItem structures
    ;
    ; These are used inside MakeImage_Entry as locals
    ;
    _4096    dd ?
    index    dd ?
    x        dd ?
    y        dd ?
    step     dq ?  ; double
ends

align 32
MakeImage_Entry:
; ---------------------------------------------------------------------------
; INPUT (stdcall):
;   Pointer to IParams instance
; ---------------------------------------------------------------------------
    push      ebp
    mov       ebp, esp
    push      ebx esi edi
    mov       ebp, [ebp + 8]    ; EBP --> IParams structure

    xor       eax, eax
    mov       [ebp + IParams.index], eax   ; index := 0
    mov       [ebp + IParams.y], eax       ; y     := 0

    mov       [ebp + IParams._4096], 4096
    mov       edi, [ebp + IParams.buffer]
    finit
    ;
    ; step := log2 (max/min)
    ;
    fld1
    fild      [ebp + IParams.max]
    fidiv     [ebp + IParams.min]
    fyl2x
    fstp      [ebp + IParams.step]
    ;
    ; for y := 0 to maxy-1
    ;
.next_y:
    and       [ebp + IParams.x], 0      ; x := 0
    ;
    ; for x := 0 to maxx-1
    ;
align 16
.inner_loop:
    mov       esi, [ebp + IParams.IterDat]
    mov       ebx, [ebp + IParams.SmD]
    ;
    ; ECX = x*maxy+y
    ;
    mov       ecx, [ebp + IParams.x]
    imul      ecx, [ebp + IParams.maxy]
    add       ecx, [ebp + IParams.y]
    ;
    ; EAX = IterDat[ecx]
    ;
    mov       eax, [esi + ecx*4]
    xor       edx, edx
    ;
    ; if (IterDat[x*maxy+y] >= max) then set RGB=0 
    ;
    cmp       eax, [ebp + IParams.max]
    jge       .set_rgb_black
    ;
    ; else if (IterDat[x*maxy+y] > 0)
    ;
    cmp       eax, edx
    jng       .increase_x
    ;
    ; ColorIndex := round(4096 * (log2((Iterdat[x*maxy+y]+1-SmD[x*maxy+y])/min)/step)) mod 4096;
    ;
    mov       edx, [ebx + ecx*4]    ; EDX = SmD[ecx]
    ;
    ; EAX = Iterdat[x*maxy+y] + 1 - SmD[x*maxy+y]
    ;
    add       eax, 1
    sub       eax, edx
    ;
    ; log2 (EAX/min)
    ;
    fld1
    push      eax
    fild      dword [esp]
    fidiv     [ebp + IParams.min]
    fyl2x

    fdiv      [ebp + IParams.step]     ; /step
    fimul     [ebp + IParams._4096]    ; *4096
    fistp     dword [esp]              ; round (...)
    pop       eax
    ;
    ; EAX mod 4096
    ;
    mov       ecx, 4096
    xor       edx, edx
    div       ecx
    ;
    ; EDX is a ColorIndex
    ;
    lea       eax, [edx + edx*2]       ; EAX = 3*ColorIndex (PalItem is 3 bytes)
    mov       ebx, [ebp + IParams.pal]
    add       ebx, eax                 ; EBX points to pal[ColorIndex]
    ;
    ; buffer[index]     := pal[ColorIndex].b;
    ; buffer[index + 1] := pal[ColorIndex].g;
    ; buffer[index + 2] := pal[ColorIndex].r;
    ;
    mov       ecx, [ebp + IParams.index]

    mov       al, [ebx + PalItem.blue]
    mov       [edi + ecx], al

    mov       al, [ebx + PalItem.green]
    mov       [edi + ecx + 1], al

    mov       al, [ebx + PalItem.red]
    mov       [edi + ecx + 2], al
    jmp       .increase_x

.set_rgb_black:
    ;
    ; buffer[index]     := 0;
    ; buffer[index + 1] := 0;
    ; buffer[index + 2] := 0;
    ;
    mov       ecx, [ebp + IParams.index]
    mov       [edi + ecx], edx
    ;
    ; Here ^^^: since index moves by 4 byte steps - I just write 4 zero bytes into buffer
    ;

.increase_x:
    add       [ebp + IParams.index], 4
    add       [ebp + IParams.x], 1
    mov       ecx, [ebp + IParams.x]
    cmp       ecx, [ebp + IParams.maxx]
    jb        .inner_loop
    ;
    ; Increase y
    ;
    add       [ebp + IParams.y], 1
    mov       ecx, [ebp + IParams.y]
    cmp       ecx, [ebp + IParams.maxy]
    jb        .next_y

.exit:
    pop       edi esi ebx ebp
    ret       4
    


Some questions:
1) What is 'align'? When I compile, I get error 'Section is not aligned enough'
2) If it don't visualize, then how to return buffer???
Post 26 Apr 2014, 20:35
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1412
Location: Toronto, Canada
AsmGuru62
1) try align 16 instead of align 32 or just comment it out.
2) I do not understand the concept of visualizing and how it is connected to returning a buffer.
Post 26 Apr 2014, 21:54
View user's profile Send private message Send e-mail Reply with quote
SeryZone



Joined: 20 Dec 2013
Posts: 38
Location: Ukraine, Kryviy Rih
SeryZone
AsmGuru62 wrote:
1) try align 16 instead of align 32 or just comment it out.
2) I do not understand the concept of visualizing and how it is connected to returning a buffer.


So, code make calculations, but for visualization we need SetDIBitsToDevice() that can make Delphi program. For using SetDibits() program should return buffer array.
Post 27 Apr 2014, 07:15
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1412
Location: Toronto, Canada
AsmGuru62
If you pass the buffer address in a structure - it will be filled by FASM code.
I would imagine the Delphi code go something like this:
Code:
{ declare the instance of IParams structure (must be exact match to FASM code) }
...
{ fill members of IParams with max, min, maxx, maxy, buffer, pal, etc.}
...
{ call MakeImage_Entry (use stdcall) and pass IParams to it }
...
{ call SetDIBitsToDevice ( ... buffer ... )}
    

buffer must be prepared before calling MakeImage_Entry.
When MakeImage_Entry fills it pith pixels - pass it to SetDIBitsToDevice ().
Post 27 Apr 2014, 11:05
View user's profile Send private message Send e-mail Reply with quote
SeryZone



Joined: 20 Dec 2013
Posts: 38
Location: Ukraine, Kryviy Rih
SeryZone
AsmGuru62 wrote:
If you pass the buffer address in a structure - it will be filled by FASM code.
I would imagine the Delphi code go something like this:
Code:
{ declare the instance of IParams structure (must be exact match to FASM code) }
...
{ fill members of IParams with max, min, maxx, maxy, buffer, pal, etc.}
...
{ call MakeImage_Entry (use stdcall) and pass IParams to it }
...
{ call SetDIBitsToDevice ( ... buffer ... )}
    

buffer must be prepared before calling MakeImage_Entry.
When MakeImage_Entry fills it pith pixels - pass it to SetDIBitsToDevice ().


Yes, you are right. As I said, SetDIBitsToDevice need buffer variable. This variable declared in FASM procedure. Need to make buffer as output parameter...
Post 27 Apr 2014, 13:10
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1412
Location: Toronto, Canada
AsmGuru62
The buffer must be declared BEFORE the FASM procedure called.
I take buffer from inside IParams structure.
Post 27 Apr 2014, 22:08
View user's profile Send private message Send e-mail Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
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 vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2020, Tomasz Grysztar. Also on YouTube, Twitter.

Website powered by rwasa.