Форум программистов, компьютерный форум, киберфорум
Наши страницы
Assembler, MASM, TASM
Войти
Регистрация
Восстановить пароль
 
 
Рейтинг: Рейтинг темы: голосов - 415, средняя оценка - 4.79
Mikl___
Автор FAQ
11376 / 5919 / 535
Регистрация: 11.11.2010
Сообщений: 10,937
25.06.2013, 15:16  [ТС] #21
Графика в DOS. Эффект RADIAL BLUR
Если вы захотите подставить какую-нибудь другую картинку, то проделайте следующую последовательность действий:
  1. нарисуйте или считайте другую двух-цветную картинку в PaintBrush
  2. сохраните ее как монохромную картинку
  3. потом как 256-цветную
  4. и вот эту 256 цветную картинку можно подставлять вместо intel.bmp.
Размер картинки естественно должен составлять 320x200, и bmp-файл не должен быть упакован, иначе у вас ничего не получиться.

intel.bmp
Идея radial blur`a проста: блюрить по кругу, то есть как в обычном случае блюрения по одной координате, но с переходом к полярным координатам.

Выберем центр нашего блюра, пусть это будет, для простоты, центр экрана. Допустим разрешение экрана 320x200. Тогда центр экрана придется на точку с координатами (160,100). Сместим начало координат в эту точку: x'=x-160; y'=y-100. Теперь рассмотрим точку М с координатами, например, (185,65), эта точка будет нашей текущей точкой, на примере ее я расскажу как выбирать вторую точку, чтобы сблюрить их цвета. Координаты в нашей новой системе координат, у этой точки будут M'(25,-35). Вектор этой точки тоже будет иметь координаты (25,-35).

Теперь надо решить, как находить вектор, по которому мы будем находить вторую точку. Есть два варианта:
  1. Изменить длину вектора нашей текущей точки так, что-бы она лежала в пределах от 1 до некоторого значения max, которое вы выберите сами, на свой вкус. (Обычно выбирают 8 чтобы использовать сдвиги).
  2. Зафиксировать длину вектора. (Опять же выбор длины остается за вами, а но можно опять выбрать 8).
Здесь используется второй вариант.
Вычислим по ней координаты вектора N, округляя до целого: N.x= 4, N.y= -6.
Тогда координаты искомой точки P будут находиться так: P(x'-N.x,y'-N.y). То есть, P(21,-29).
Теперь осталось сблюрить точки M и P, и записать получившиеся значение в точку M.
Осталось рассказать в каком порядке надо обрабатывать точки, чтобы получить правильное, радиально-сблюренное изображение.
Разобьем экран на четыре сектора:
Рассмотрим сектор #1:
Обрабатывать пиксели по координате X, надо в порядке: 1,2,3..., а по координате Y, по-строчно, в порядке: A,B,C... .
По такому же принципу надо поступать и в других секторах: обрабатывать сектор по строкам, начиная с ближней к центру блюра, а в строке тоже двигаться от центра блюра.
Естественно, линии с координатами центра блюра надо тоже обработать. Ну вот и все, загляните в прилагаемые исходники, что бы посмотреть, как можно прооптимизировать этот эффект.
Взято здесь, текст программы немного переделан, в результате СОМ-файл уменьшился с 1242 байт до 810 байт
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
; masm dos com #
.model tiny
.code
.386
org 100h
WIDTH_SCREEN    equ 320
HEIGHT_SCREEN   equ 200
SCREENSIZE  equ WIDTH_SCREEN*HEIGHT_SCREEN
MEMBLOCKSIZE    equ SCREENSIZE/16
IMPUT_STATUS_0  equ 3DAh    ;регистр статуса ввода 0
VGA_SEGMENT equ 0A000h
 
time        equ dword ptr [bp-4]
n       equ dword ptr [bp-8]
y       equ word ptr [bp-10]
vx      equ word ptr [bp-12]
p1      equ word ptr [bp-14]
screen      equ word ptr [bp-16]
pics        equ word ptr [bp-18]
bbx     equ dword ptr [bp-22]
bby     equ dword ptr [bp-26]
 
start:      mov sp,0DF0h
        mov ah,4Ah      ;ADJUST MEMORY BLOCK SIZE (SETBLOCK)
        mov bx,0DFh     ;ES = segment address of block  to change
        int 21h         ;BX = new size in paragraphs
        enter 26,0
        mov time,0
        mov bx,MEMBLOCKSIZE ;4000
        mov ah,48h      ;ALLOCATE MEMORY
        int 21h         ;BX = number of 16-byte paragraphs desired      
        mov screen,ax       ;screen[64000]
        mov bx,MEMBLOCKSIZE ;4000
                mov ah,48h      ;ALLOCATE MEMORY
        int 21h         ;BX = number of 16-byte paragraphs desired
        mov pics, ax        ;pics[64000]
        mov bx,MEMBLOCKSIZE*2
                mov ah,48h      ;ALLOCATE MEMORY
        int 21h         ;BX = number of 16-byte paragraphs desired
        mov es,ax ;es=sqr       sqr[128000]
        mov fs,ax ;fs=sqr
        add ax,4000
        mov gs,ax ;gs=sqr+64000/16
        mov ax,13h
        int 10h     
;initsqr--------------------------------------
        mov si,WIDTH_SCREEN-1
a0:     mov y,HEIGHT_SCREEN-1
@@:     mov ax,y
        mul ax
        movzx ecx,ax;ecx=y*y
        movsx eax,si
        mul eax
        add eax,ecx;eax=x*x + y*y
        mov n,eax
        fild n
        fsqrt
        fistp n
        mov eax,n;n=sqrt(x*x + y*y)
        shr eax,3
        inc eax
        mov n,eax;n=(sqrt(x*x + y*y))/8 + 1
        imul di,y,WIDTH_SCREEN
        mov ax,si
        add di,ax;di=y*320 + x
        cwd
        idiv word ptr n;ax=x/((sqrt(x*x + y*y))/8 + 1)
        mov gs:[di],al
        mov ax,y
        cwd
        idiv word ptr n;ax=y/((sqrt(x*x + y*y))/8 + 1)
        stosb   ;mov es:[di],al
        dec y
        jns @b
        dec si
        jns a0
;readbmp-----------------------------------------------------------
        mov dx, offset aIntel_bmp ; "intel.bmp"
        mov ax, 3D00h     ; OPEN DISK FILE WITH HANDLE
        int 21h; DS:DX  -> ASCIZ filename
        mov bx,ax
        xor cx,cx
        mov dx,1078;заголовок и палитра BMP-файла
        mov ax,4200h; MOVE FILE READ/WRITE POINTER (LSEEK)
        int 21h ; AL = method: offset from beginning of file                    
        push    ds
        mov ds, pics
        mov di,HEIGHT_SCREEN-1
@@:     imul    dx,di,WIDTH_SCREEN
        mov cx,WIDTH_SCREEN
        mov ah, 3Fh; READ FROM FILE WITH HANDLE
        int 21h; BX = file handle, CX = number of bytes to read, DS:DX -> buffer
        dec di
        jnz @b
        push    ds
        pop     es;es=pics  di=0
        pop ds
        mov ah, 3Eh; CLOSE A FILE WITH HANDLE
        int 21h; BX = file handle
        mov cx,64000
            inc si;si=0FFFFh+1=0
a1:     lods byte ptr es:[si];c=fgetc(in)
        dec al
        jz  @f
        or  al,-1
@@:     not al
        stosb ;if(c==1)pics[x+320*y]=255 else pics[x+320*y]=0
                loop    a1
;blurpics-----------------------------------------------------
        mov di, WIDTH_SCREEN
        mov cx,63040
@@:     movzx   ax, byte ptr es:[di-WIDTH_SCREEN]
        add al, byte ptr es:[di-1]
        adc ah, 0
        add al, byte ptr es:[di+1]
        adc ah, 0
        add al, byte ptr es:[di+WIDTH_SCREEN]
        adc ah, 0
        shr ax, 2
        stosb
        loop    @b
;setcolortable--------------------------------------------
        mov cx, 255
@@:     mov bl, 255
        sub bl, cl
        shr bl, 2
        mov dx, 3C8h
        mov al, cl;color
                mov ah, bl;red
        out dx, ax
        inc dx
        mov al,bl;green
        out dx,al
        out dx,al
        dec cx
        jns @b
;-----------------------------------------------------------
a2: fld time
    fadd    const005
    fst time; time+=0,05
    fmul    const1_1;time*1,1
    fadd    const1_5;time*1,1+1,5
    fsin                      ;sin(time*1,1+1,5)
    fmul    const120;120*sin(time*1,1+1,5)
    fistp   bbx   ;bbx=120*sin(time*1,1+1,5)
    add bbx,WIDTH_SCREEN/2;bbx=160+120*sin(time*1,1+1,5)
    fld time
    fmul    const08;time*0,8
    fadd    const1_1;1,1+time*0,8
    fcos                      ;cos(1,1+time*0,8)
    fmul    const90;90*cos(1,1+time*0,8)
    fistp   bby  ;bby=90*cos(1,1+time*0,8)
    add bby,HEIGHT_SCREEN/2;bby=100+90*cos(1,1+time*0,8) 
;100 и 160 координаты середины экрана
;copypicsvirtual        
    push ds
    mov ds,pics
    mov es,screen
    xor si,si
    xor di,di
    mov cx,SCREENSIZE/4
    rep movsd
    pop ds
;radialblurscreen
    xor dx,dx
    mov bx,word ptr bby
    dec bx         ; bx = bby - 1
    imul di,bx,WIDTH_SCREEN; di = (bby - 1)*320
    add di,word ptr bbx; di = (bby - 1)*320 + bbx
    dec di        ; di = (bby - 1)*320 + bbx - 1
a3: mov cx,word ptr bbx
@@: mov ah,0
    mov al,es:[di]
    mov p1,ax
    mov si,dx
    mov al,gs:[si]
    mov vx,ax        ; vx = gs[t]
    lods byte ptr fs:[si]
    imul ax,WIDTH_SCREEN;      ax=320*fs:[t]
    mov si,di
    add si,vx
    add si,ax
    mov ah,0
    lods byte ptr es:[si];movzx ax,byte ptr es:[si]
    add ax,p1
    shr ax,1
    stosb;mov es:[di], al
    dec di
    dec di
    inc dx
    dec cx
    jnz @b
    sub di,WIDTH_SCREEN
    add di,word ptr bbx
    mov ax,WIDTH_SCREEN
    sub ax,word ptr bbx
    add dx,ax
    dec bx
    jns a3
        mov ax,word ptr bby
        dec ax
        imul di,ax,WIDTH_SCREEN
        add di,word ptr bbx
        xor dx,dx
        mov cx,word ptr bby
a4:     mov bx,word ptr bbx
@@:     movzx   ax, byte ptr es:[di]
        mov p1,ax
        mov si,dx
        movzx   ax,byte ptr gs:[si]
        mov vx,ax
        movzx   ax,byte ptr fs:[si]
        imul ax,WIDTH_SCREEN
        mov si, di
        sub si, vx
        add si, ax
        movzx   ax, byte ptr es:[si]
        add ax, p1
        shr ax, 1
        stosb
        inc dx
        inc bx
                cmp bx,WIDTH_SCREEN
        jl  @b
        sub di,WIDTH_SCREEN*2
        add di,word ptr bbx
        mov ax,word ptr bbx
        add dx,ax
        dec cx
        jnz a4
        mov cx,word ptr bby
        imul di,cx,WIDTH_SCREEN
        add di,word ptr bbx
        dec di
        xor dx,dx
a5:     mov bx,word ptr bbx
@@:     movzx   ax, byte ptr es:[di]
        mov p1,ax
        mov si,dx
        movzx   ax, byte ptr gs:[si]
        mov vx,ax
        movzx   ax, byte ptr fs:[si]
        imul ax,WIDTH_SCREEN
        mov si, di
        add si, vx
        sub si, ax
        movzx   ax, byte ptr es:[si]
        add ax, p1
        shr ax, 1
        stosb;mov   es:[di], al
        dec     di
        dec di
        inc dx
        dec bx
        jnz @b
        add di, WIDTH_SCREEN
        add di,word ptr bbx
        mov ax, WIDTH_SCREEN
        sub ax,word ptr bbx
        add dx,ax
        inc cx
                cmp cx,HEIGHT_SCREEN
        jl  a5
        mov cx,word ptr bby
        imul    di,cx,WIDTH_SCREEN
        add di,word ptr bbx
        xor dx,dx
a6: mov bx,word ptr bbx
@@:     movzx   ax, byte ptr es:[di]
        mov p1,ax
        mov si,dx; t
        mov ah,0
        mov al,gs:[si]
        mov vx,ax
        lods    byte ptr fs:[si]
        mov dx,si
        imul ax,WIDTH_SCREEN
        mov si, di
        sub si, vx
        sub si, ax
        movzx   ax, byte ptr es:[si]
        add ax, p1
        shr ax, 1
        stosb
        inc bx
                cmp bx,WIDTH_SCREEN
        jl  @b
        mov ax,word ptr bbx
        add di,ax
        add dx,ax
        inc cx
        cmp cx,HEIGHT_SCREEN
        jl  a6
;blurscreen --------------------------------------
        mov di, WIDTH_SCREEN
        mov cx,63040
loc_104E7:  movzx   ax, byte ptr es:[di-WIDTH_SCREEN]
        add al, byte ptr es:[di-1]
        adc ah, 0
        add al, byte ptr es:[di+1]
        adc ah, 0
        add al, byte ptr es:[di+WIDTH_SCREEN]
        adc ah, 0
        shr ax, 2
        stosb
        loop    loc_104E7
;copyvirtualscreen
        mov dx, IMPUT_STATUS_0
WaitVerticalSync:in al,dx
            test al,8
            jz WaitVerticalSync
WaitNotVerticalSync:in al,dx
            test al,8
            jnz WaitNotVerticalSync
        mov cx,SCREENSIZE/4
        xor si,si
        push ds
        push es
        pop ds
        xor di,di
            push VGA_SEGMENT
        pop es
        rep movsd
        xor ax,ax
        mov ds,ax
        mov ax,ds:[41Ah]
        sub ax,ds:[41Ch]
        pop ds
        jz a2
exit:       mov ax,3
        int 10h     ; - VIDEO - SET VIDEO MODE
        int 20h
 
const005    dd 0.05
const08     dd 0.8
const1_1    dd 1.1
const1_5    dd 1.5
const90     dd 90.0
const120    dd 120.0
aIntel_bmp  db 'intel.bmp',0
end start
Исходный текст, СОМ-файл и картинка здесь
2
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
25.06.2013, 15:16
Я подобрал для вас темы с готовыми решениями и ответами на вопрос FAQ для раздела Assembler, MASM, TASM (Assembler):

Глоссарий для раздела Assembler, MASM, TASM
Thread, AFP, Charles Kludge, 6a6kin, Убежденный, Ethereal, выкладывайте свои...

Организация тем в разделе Assembler, MASM, TASM
Всем привет! Друзья, возник вопрос: как лучше организовать темы в разделе...

Полезные макросы для MASM и TASM
Не претендую на создание чего-то нового и гениального, но макросы довольно...

MASM, TASM, FASM: что выбрать для программирования в ядре
Какой асемлер выбрать для проганья в едре? вынь

Видеоуроки по Ассемблеру MASM/TASM (для DOS) на русском языке
Всем доброго времени суток. Вобщем, ищу видеоуроки на русском языке по...

Есть ли компиляторы Tasm или Masm для 64-разрядных систем
Есть ли компиляторы Tasm или Masm для 64-разрядной с-мы??? если есть просьба...

62
Mikl___
Автор FAQ
11376 / 5919 / 535
Регистрация: 11.11.2010
Сообщений: 10,937
25.06.2013, 15:16  [ТС] #22
Графика в DOS. Эффект след от водомерки на поверхности воды
Этап первый
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
; masm dos com #
.model tiny
.code
.386
org 100h
PALSIZE equ 300h
WIDTH_SCREEN    equ 320
HEIGHT_SCREEN   equ 200
SCREENSIZE  equ WIDTH_SCREEN*HEIGHT_SCREEN
IMPUT_STATUS_0  equ 3DAh    ;регистр статуса ввода 0
VGA_SEGMENT equ 0A000h
 
start:          mov ax, 13h;установили необходимый режим 320х200х256
        int 10h     
        mov ax, 4309h;создать палитру
        mov dx, 3D4h
        out dx, ax
        
        xor bx, bx
        mov dl, 0C8h
        mov al, 0
        out dx, al
        inc dx
 
@0:     mov al, 0
        cmp bl, 128
        jnb @f
        mov al, 127
        sub al, bl
        shr al, 1
 
@@:     out dx, al
        out dx, al
        mov cl, bl
        shr cl, 1
        mov al, 127
        sub al, cl
        shr al, 1
        out dx, al
        inc bl
        jnz @0
;-----------------------------------------
    mov ax,cs
    add ax,1000h
    mov fs,ax   ; watertable1=AX
    mov es,ax
    add ax,2000
    mov gs,ax   ; watertable2=AX+WATERTABLE PARAGRAPH SIZE
;очистить буфер
    xor di,di
    mov cx, SCREENSIZE/4    ; CX=WATERTABLESIZE
    xor eax, eax
    rep stosd       ; initialize wt1 and wt2
    push    VGA_SEGMENT
    pop es
main_loop:mov dx,IMPUT_STATUS_0
WaitVerticalSync:in al,dx; притормозить вывод на экран до следующего кадра
    test al,8
    jz WaitVerticalSync
WaitNotVerticalSync:in al,dx
    test al,8
    jnz WaitNotVerticalSync
;Вывод на экран из буфера
    xor di, di
    mov cx, SCREENSIZE/4
@@: mov ax, fs:[di]
    sub ax, fs:[di-2]   
    sar ax, 3; AX=(FS:[DI]-FS:[DI-2])/8
    add al, HEIGHT_SCREEN/2
    stosb
    stosb
    loop    @b  
;Эффект воды
    mov di,WIDTH_SCREEN+2
    mov cx,SCREENSIZE/4 - WIDTH_SCREEN
@@: mov ax,fs:[di-WIDTH_SCREEN]
    add ax,fs:[di-2]
    add ax,fs:[di+2]
    add ax,fs:[di+WIDTH_SCREEN] 
    sar ax,1; AX=(FS:[DI-320]+FS:[DI-2]+FS:[DI+2]+FS:[DI+320])/2
    sub ax,gs:[di]  ; AX-=GS:[DI]
    mov bx,ax
    sar bx,3
    sub ax,bx
    mov gs:[di],ax
    add     di,2
    loop    @b  
    push    gs
    push    fs
    pop gs
    pop fs  ; watertable2 <-> watertable1
;перемещение объекта
    mov bx,offset locobj
    cmp word ptr [bx+4],WIDTH_SCREEN*2
    sbb dx,dx
    or  dl,1; IF( DS:[BX+4] > 640 ) DS:[BX]--
    sub     [bx],dx; ELSE DSWORD[BX]++
    cmp word ptr [bx+6],HEIGHT_SCREEN*2
    sbb dx,dx   ; IF( DS:[BX+6] > 400 ) DS:[BX+2]--
    or  dl,1
    sub     [bx+2],dx; ELSE DS:[BX+2]++
    mov ax,[bx]
    add [bx+4],ax   ; DS:[BX+4]+=DS:[BX]
    mov ax,[bx+2]
    add [bx+6],ax   ; DS:[BX+6]+=DS:[BX+2]
    mov di,[bx+4]
    shr di,3        ; di=x-координата
    mov ax,[bx+6]
    shr ax,3
    imul    ax,WIDTH_SCREEN/2;ax=y-координата    
    add di,ax       ; di=x+y
    add di,di       ; DI*= 2
 
    mov dword ptr gs:[di],3840384h; GS=watertable1
    mov dword ptr fs:[di],1C401C4h; FS=watertable2
    add di,4
    mov ah,1
    int 16h     ;нажали на любую клавишу?
    jz  main_loop
    mov ax, 3           ;восстанавливаем текстовый режим
    int 10h     
    retn                    ;завершаем программу
locobj dw 5,0,80,40
end start
Размер COM-файла 265 байт. Во вложении исходный текст и СОМ-файл.
Этап второй
. Усложняем программу. Теперь у нас водомерка на поверхности воды и след от рыбы, которая пытается поймать водомерку
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
; masm dos com #
.model tiny
.code
.386
org 100h
PALSIZE equ 300h
WIDTH_SCREEN    equ 320
HEIGHT_SCREEN   equ 200
SCREENSIZE  equ WIDTH_SCREEN*HEIGHT_SCREEN
IMPUT_STATUS_0  equ 3DAh    ;регистр статуса ввода 0
VGA_SEGMENT equ 0A000h
palette equ byte ptr ende
 
start:          mov ax, 13h;установили необходимый режим 320х200х256
        int 10h     
        mov ax, 4309h;создать палитру
        mov dx, 3D4h
        out dx, ax
        
        xor bx, bx
        mov di,offset palette
        push    ds
        pop es
 
@0:     xor ax, ax
        cmp bl, 128
        jnb @f
        mov al, 127
        sub al, bl
        shr al, 1
        mov ah, al
@@:     stosw
        mov cl, bl
        shr cl, 1
        mov al, 127
        sub al, cl
        shr al, 1
        stosb
        inc bl
        jnz @0
        xor ax, ax
        mov cx, PALSIZE
        mov si, offset palette+3
        mov dx, 3C8h
        out dx, al
        inc dx
        rep outsb
        push    ds
        pop ax
        add ah, 10h
        mov fs,ax   ; watertable1=AX
        mov es,ax
        add ax,2000
        mov gs,ax   ; watertable2=AX+WATERTABLEPARAGRAPHSIZE
        xor di,di
        mov cx, SCREENSIZE/4    ; CX=WATERTABLESIZE
        xor eax, eax
        rep stosd       ; initialize wt1 and wt2
 
    xor si, si
main_loop:mov dx,IMPUT_STATUS_0
WaitVerticalSync:in al,dx         ; притормозить
    test al,8
    jz WaitVerticalSync
WaitNotVerticalSync:in al,dx
    test al,8
    jnz WaitNotVerticalSync
;ShowWater
    push    VGA_SEGMENT
    pop es
    xor di, di
    mov cx, SCREENSIZE/4
@@: mov ax, fs:[di]
    sub ax, fs:[di-2]   
    sar ax, 3; AX=(FS:[DI]-FS:[DI-2])/8
    add al, HEIGHT_SCREEN/2
    stosb
    stosb
    loop    @b  
;CalcWaterEffect
    mov di,WIDTH_SCREEN+2
    mov cx,(HEIGHT_SCREEN-4)*WIDTH_SCREEN/4
@@: mov ax,fs:[di-WIDTH_SCREEN]
    add ax,fs:[di-2]
    add ax,fs:[di+2]
    add ax,fs:[di+WIDTH_SCREEN] 
    sar ax,1; AX=(FS:[DI-320]+FS:[DI-2]+FS:[DI+2]+FS:[DI+320])/2
    sub ax,gs:[di]  ; AX-=GS:[DI]
    mov bx,ax
    sar bx,3
    sub ax,bx
    mov gs:[di],ax
    add     di,2
    loop    @b  
    push    gs
    push    fs
    pop gs
    pop fs  ; watertable2 <-> watertable1
 
    mov bx,offset small 
    call    CALCADR
    mov dword ptr gs:[di],3840384h; GS=watertable1
    mov dword ptr fs:[di],1C401C4h; FS=watertable2
    add di,4
    test    si,100h ; IF(SI&256)
    jz  @5
    mov bx, offset big  
    call    CALCADR
    mov cx, 5
@4: mov bx, 5
@@: mov word ptr gs:[di],384h; GS=watertable1
    mov word ptr fs:[di],1C4h; FS=watertable2
    add     di,2
    dec bx
    jnz @b  
    add di,WIDTH_SCREEN-10
    loop    @4
@5: add     si,2
    mov ah,1
    int 16h     ; KEYBOARD - CHECK BUFFER, DO NOT CLEAR
    jz  main_loop
    mov ax, 3
    int 10h     ; - VIDEO - SET VIDEO MODE
    retn
 
CALCADR proc
    cmp word ptr [bx+4],WIDTH_SCREEN*2
    sbb dx,dx
    or  dl,1; IF(DSWORD[BX+4] > XSIZE*4) DSWORD[BX]--
    sub     [bx],dx; ELSE DSWORD[BX]++
    cmp word ptr [bx+6],HEIGHT_SCREEN*2
    sbb dx,dx   ; IF(DSWORD[BX+6] > YSIZE*4) DSWORD[BX+2]--
    or  dl,1
    sub     [bx+2],dx; ELSE DSWORD[BX+2]++
    mov ax,[bx]
    add [bx+4],ax   ; DSWORD[BX+4]+=DSWORD[BX]
    mov ax,[bx+2]
    add [bx+6],ax   ; DSWORD[BX+6]+=DSWORD[BX+2]
    mov di,[bx+4]
    shr di,3        ; DI=DSWORD[BX+4]>>3
    mov ax,[bx+6]
    shr ax,3
    imul    ax,WIDTH_SCREEN/2
    add di,ax       ; DI+=AX
    add di,di       ; DI+=DI
    retn
CALCADR endp
 
small dw 5,0,80,40
big   dw -3,0,304,120
ende:
end start
Размер COM-файла 342 байт. Во вложении исходный текст и СОМ-файл.
Заключительный этап
Довольно загадочный, для непосвященных, но простой, эффект. В основе лежит, все тот же Blur. Я выбрал 8-пиксельный блюр, так как он дает более красивую картинку.

Установим палитру так:
0 http://www.cyberforum.ru/cgi-bin/latex.cgi?\rightarrow 255
Синий Белый
Заведем два буфера: buf1 и buf2, и указатели на них: buf1_ptr и buf2_ptr. Важно понять, что, хоть указатель называется buf1_ptr, но он может указывать совсем не на buf1, а на buf2. Аналогично для buf2_ptr. Далее по тексту, если массив будет называться buf1_ptr, то имеется в виду тот массив, на который, в данный момент, ссылается buf1_ptr. Тоже самое подразумевается под массивом buf2_ptr.

Далее описывается последовательность, которую надо проделать, для расчета очередного кадра:
  1. Если buf1_ptr, действительно указывает на buf1, то заносим в buf1_ptr точки возмущения.
  2. Проходим по всем точкам массива buf1_ptr и buf2_ptr вот таким алгоритмом:
    2.1. Складываем значения точек окружающих текущую, в массиве buf1_ptr.
    2.2. Делим эту сумму на 4.
    2.3 Вычитаем из этой суммы значение текущей точки массива buf2_ptr.
    2.4 Если результат меньше 0, то результат равен 0.
    2.5 Записываем результат в текущую точку массива buf2_ptr.
  3. Теперь меняем местами buf1 и buf2 (собственно для этого нам и были нужны указатели):
    C
    1
    2
    3
    
      long temp_ptr=buf1_ptr;
      buf1_ptr=buf2_ptr;
      buf2_ptr=temp_ptr;
  4. Выводим на экран buf2_ptr.
В результате получим эффект поверхности воды с всплывающими и лопающимися пузырьками и бегающими водомерками.
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
; masm dos com #
.model tiny
.code
.686
org 100h
VGA_SEGMENT equ 0A000h
IMPUT_STATUS_0  equ 3DAh
SCREEN_WIDTH    equ 320
SCREEN_HEIGHT   equ 200
SCREENSIZE  equ SCREEN_HEIGHT*SCREEN_WIDTH
MEMBLOCKSIZE    equ SCREENSIZE/16
yd      equ dword ptr [bp-4]
xd      equ dword ptr [bp-8]
buf1        equ word ptr [bp-10]
buf2        equ word ptr [bp-12]
buf1_ptr    equ word ptr [bp-14]
buf2_ptr    equ word ptr [bp-16]
temp1       equ word ptr [bp-18]
temp2       equ word ptr [bp-20]
        
start:      mov sp, 1260h
        mov ah, 4Ah; ADJUST MEMORY BLOCK SIZE (SETBLOCK)
        mov bx, 126h; ES = segment address of block to change
        int 21h; BX = new size  in paragraphs
        enter   20,0
        mov bx, MEMBLOCKSIZE
        mov ah, 48h; ALLOCATE MEMORY
        int 21h; BX = number of 16-byte paragraphs desired
        mov buf1, ax
        mov buf1_ptr, ax
        mov es,ax
        xor eax,eax
        xor di, di
        mov cx,SCREENSIZE/4
        rep stosd
        rdtsc
        mov dword ptr temp1, eax
        mov bx, MEMBLOCKSIZE
        mov ah, 48h; ALLOCATE MEMORY
        int 21h; BX = number of 16-byte paragraphs desired
        mov buf2, ax
        mov buf2_ptr, ax
        mov es,ax
        xor eax,eax
        xor di,di
        mov cx,SCREENSIZE/4
        rep stosd
        mov ax,13h
        int 10h     ; - VIDEO - SET VIDEO MODE
;setcolortable--------------------------------
        mov cx,255
@@:     mov dx, 3C8h
        mov al,cl;color
        mov ah,cl
        sar ah,2;red
        out dx, ax
        inc dx
        mov al,ah;gren=red
        out dx, al
        mov al,63;blue
        out dx, al
        dec cx
        jns @b
        fldz
;-------------------------------------------
main:           mov ax, buf1
        cmp buf1_ptr, ax
        jnz @f
        mov es, ax;buf1
        mov ax, temp2
        mov cx, temp1
        shld    ax,cx,8
        shl cx,8
        rcr ax, 1
        rcr cx, 1
        add temp1, cx
        adc ax, temp2
        add word ptr temp1,25321
        adc ax,13849
        mov temp2, ax
        movsx   eax, ax
        add eax, eax
        xor edx, edx
        mov ecx,62000
        div ecx
        mov si, dx ;RAND()<<1%62000
        add si, SCREEN_WIDTH
        mov dword ptr es:[si], 7FFFFF7Fh
        mov dword ptr es:[si+SCREEN_WIDTH],0FFFFFFFFh
        mov dword ptr es:[si+SCREEN_WIDTH*2],0FFFFFFFFh
        mov dword ptr es:[si+SCREEN_WIDTH*3],7FFFFF7Fh
        fld st;time
        fld st
        fld     st
        fld     st
        fmul    const2_5
        fadd    const1_5
        fsin
        fmul    const120
        fistp   xd      
        add xd,160;xd=sin(time*2.5+1.5)*120+160
        ;fld    time
        fmul    const1_5
        fadd    const2_5
        fcos
        fmul    const60
        fistp   yd      
        add yd,100;yd=cos(time*1.5+2.5)*60+100
        imul    si,word ptr yd,SCREEN_WIDTH
        add si, word ptr xd
        mov word ptr es:[si+SCREEN_WIDTH], 0FFFFh
        mov word ptr es:[si+SCREEN_WIDTH*2], 0FFFFh
        ;fld    time
        fmul    const1_5
        fadd    const2_5
        fsin
        fmul    const120
        fistp   xd      
        add xd,160;xd=160+120*sin(time*1.5+2.5)
        fmul    const2_5
        fadd    const1_5
        fcos
        fmul    const60
        fistp   yd      
        add yd,100;yd=100+60*cos(time*2.5+1.5)
        imul    si,word ptr yd,SCREEN_WIDTH
        add si, word ptr xd
        mov word ptr es:[si+SCREEN_WIDTH], 0FFFFh
        mov word ptr es:[si+SCREEN_WIDTH*2], 0FFFFh
        fadd    const003;time=+0.03
;waterscreen--------------------------------
@@:     mov fs, buf1_ptr
        mov es, buf2_ptr
        mov di, SCREEN_WIDTH+1
        mov cx,0F8BFh-SCREEN_WIDTH-1
loc_103BE:  movzx   ax, byte ptr fs:[di-SCREEN_WIDTH-1]
        add al, byte ptr fs:[di-SCREEN_WIDTH]
        adc ah, 0
        add al, byte ptr fs:[di-SCREEN_WIDTH+1]
        adc ah, 0
        add al, byte ptr fs:[di-1]
        adc ah, 0
        add al, byte ptr fs:[di+1]
        adc ah, 0
        add al, byte ptr fs:[di+SCREEN_WIDTH-1]
        adc ah, 0
        add al, byte ptr fs:[di+SCREEN_WIDTH]
        adc ah, 0
        add al, byte ptr fs:[di+SCREEN_WIDTH+1]
        adc ah, 0
        shr ax, 2
        sub al, byte ptr es:[di]
        sbb ah, 0
        test    ax, ax
        jns @f
        xor ax, ax
@@:     stosb
                loop loc_103BE
        mov ax, buf1_ptr
        xchg    ax, buf2_ptr
        mov buf1_ptr, ax
        push    ds
        mov ds,ax;buf1_ptr
;copyvirtualscreen--------------------------
        mov dx,IMPUT_STATUS_0 ;=3DAh
WaitVerticalSync:in al, dx
        test al, 8
        jz WaitVerticalSync
WaitNotVerticalSync:in al, dx
        test al, 8
        jnz WaitNotVerticalSync
        mov cx,SCREENSIZE/4
        xor si,si
        xor di,di
        push    VGA_SEGMENT
        pop es
        rep movsd
        mov ds,cx;ds=0
        mov ax, ds:[41Ah]; было ли нажатие на клавиатуру?
        sub ax, ds:[41Ch]
        pop ds
        jz  main
exit:       mov ax, 3
        int 10h     ; - VIDEO - SET VIDEO MODE
        int 20h
;---------------------------------
const1_5    dd 1.5
const2_5    dd 2.5
const60     dd 60.0
const120    dd 120.0
const003    dd 0.03
end start
Неплохо бы было добавить background (какую-нибудь картинку), да еще чтоб волны искажали его. Что ж нет ничего невозможного...Добавим два буфера: pics и screen_end. В pics будем хранить картинку, которую используем в качестве background`а. А screen_end мы будем выводить на экран. Палитру надо взять из считываемой картинки.
Как же нам добиться эффекта искажения, из-за преломления лучей света на границе двух разнородных сред? Сейчас подумаем.
Берем в руки учебник физики, ищем раздел "Оптика" и находим формулу расчета угла преломленного луча. Применив знания, полученные на уроках геометрии, выведем формулу, с помощью которой будем находить смещение, относительно текущего пикселя, по которому находиться пиксель, который и надо вывести на экран вместо текущего:
Код
D=h*tg(arcsin(0.75*sin(a)))
D-искомое смещение, h-высота слоя воды (высота волны), а-угол между нормалью к поверхности воды в данной точке и вектором наблюдения.
Нужно искать тангенс арксинуса синуса, да еще расчет нормали к поверхности, плюс это надо разложить на нахождение смещения по X и Y, сложновато...
Конечно, можно воспользоваться табличным разложением, но таблица получиться слишком уж большой. Как же быть? Взглянем еще раз на нашу формулу и подумаем, нельзя ее как нибудь сократить.
Допустим, что наш тангенс арксинуса синуса, есть величина величина постоянная и равная, например 0.25. (Воспользуемся калькулятором). Опыт показал, что такое допущение вполне возможно, если ширина волны небольшая. Тогда формула значительно упроститься и будет выглядеть так:
D=h/4
Теперь, чтобы найти координаты нужного пикселя, надо прибавить к текущим, D:
x'=x+D; y'=y+D;
В наш алгоритм добавиться пункт 2.5.
2.5. Делим результат на 4, полученное значение прибавляем к текущим X и Y. Если X, больше максимально допустимого значения, то X приравняем максимальному допустимому значению. Аналогичную проверку надо сделать и для Y. Считываем по этим координатам из массива pics, число и записываем по текущим координатам в screen_end.
Теперь модифицируем этот алгоритм чтобы получить более реалистичное изображение.
Недостаток описанного выше метода - смещение не зависит от наклона нормали к поверхности воды. Как исправить этот недостаток, не усложняя алгоритм? Довольно просто. Достаточно знать куда наклонена нормаль, тогда сможем смещать координаты в соответствии с этим направлением. Для того чтобы найти смещения по x и y, используем следующие формулы:
C
1
2
Dx=(buf1_ptr[x+1][y]-buf1_ptr[x-1][y])>>2;
Dy=(buf1_ptr[x][y+1]-buf1_ptr[x][y-1])>>2;
Как надо изменить пункт 2.6., вы наверняка догадаетесь сами.
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
; masm dos com #
.model tiny
.code
.686
org 100h
IMPUT_STATUS_0  equ 3DAh
VGA_SEGMENT equ 0A000h
SCREEN_HEIGHT   equ 200
SCREEN_WIDTH    equ 320
SCREENSIZE  equ SCREEN_WIDTH*SCREEN_HEIGHT
MEMBLOCKSIZE    equ SCREENSIZE/16
yd      equ dword ptr [bp-4]
xd      equ dword ptr [bp-8]
buf1        equ word ptr [bp-10]
buf2        equ word ptr [bp-12]
screen_end  equ word ptr [bp-14]
pics        equ word ptr [bp-16]
buf1_ptr    equ word ptr [bp-18]
buf2_ptr    equ word ptr [bp-20]
temp1       equ word ptr [bp-22]
temp2       equ word ptr [bp-24]
pal     equ byte ptr [bp-1048]
 
 
start:      mov sp, 1260h
        mov ah, 4Ah; ADJUST MEMORY BLOCK SIZE (SETBLOCK)
        mov bx, 126h; ES = segment address of block to change
        int 21h; BX = new size in paragraphs
 
        enter 1048, 0
        rdtsc
        mov dword ptr temp1,eax
        mov bx,MEMBLOCKSIZE
        mov ah,48h; ALLOCATE MEMORY
        int 21h; BX = number of 16-byte paragraphs desired
        mov buf1, ax
        mov buf1_ptr, ax
        mov es,ax
        xor eax,eax
        xor di,di
        mov cx,SCREENSIZE/4
        rep stosd
        mov bx,MEMBLOCKSIZE
        mov ah,48h; ALLOCATE MEMORY
        int 21h; BX = number of 16-byte paragraphs desired
        mov buf2, ax
        mov buf2_ptr, ax
        mov es,ax
        xor eax,eax
        xor di, di
        mov cx,SCREENSIZE/4
        rep stosd
        mov bx,MEMBLOCKSIZE
        mov ah,48h; ALLOCATE MEMORY
        int 21h; BX = number of 16-byte paragraphs desired
        mov screen_end,ax
        mov es,ax
        xor eax,eax
        xor di,di
        mov cx,SCREENSIZE/4
        rep stosd
        mov bx,MEMBLOCKSIZE
        mov ah,48h; ALLOCATE MEMORY
        int 21h; BX = number of 16-byte paragraphs desired
        mov pics,ax
        mov ax,13h
        int 10h     ; - VIDEO - SET VIDEO MODE
;readbmp---------------------------------------------------------
        mov dx,offset filename; OPEN DISK FILE WITH HANDLE
        mov ax,3D00h       ; DS:DX  -> ASCIZ filename
        int 21h     ; AL = access mode 0 - read
        mov bx,ax;bx=handle
        xor cx,cx
        mov dx,54;размер заголовка BMP-файла
        mov ax,4200h; MOVE FILE READ/WRITE POINTER (LSEEK)
        int 21h; AL = method: offset from beginning of file
        lea dx,pal
        mov cx,1024
        mov ah,3Fh; READ FROM FILE WITH HANDLE
        int 21h; BX = file handle, CX = number of bytes to read
                ; DS:DX -> buffer
        mov di,1023
@@:     shr byte ptr pal[di],2
        dec di
        jns @b
        push ds
        mov ds,pics
        mov di,SCREEN_HEIGHT-1
@@:     imul dx,di,SCREEN_WIDTH
        mov cx,SCREEN_WIDTH
        mov ah,3Fh; READ FROM FILE WITH HANDLE
        int 21h; BX = file handle, CX = number of bytes to read
                ; DS:DX -> buffer
        dec di
        jns @b
        pop ds
        mov ah,3Eh; CLOSE A FILE WITH HANDLE
        int 21h ; BX = file handle
;--------------------------------------------------
        mov cx,255
@@:     mov di,cx
        shl di,2
        mov dx,3C8h
        mov al,cl;color
        mov     ah,pal[di+2];red
        out dx,ax
        inc dx
        mov al,pal[di+1];green
        out dx,al
        mov al,pal[di];blue
        out dx,al
        dec cx
        jns @b
;-----------------------------------------------------
        fldz    ;time=0
main:       mov ax,buf1
        cmp buf1_ptr,ax
        jnz @f
        mov es,ax;buf1
        mov ax,temp2
        mov cx,temp1
        shld ax,cx,8
        shl cx,8
        rcr ax,1
        rcr cx,1
        add temp1,cx
        adc ax,temp2
        add word ptr temp1,25321
        adc ax,13849
        mov temp2,ax
        movsx eax,ax
        add eax,eax
        xor edx,edx
        mov ecx,62000
        div ecx
        mov si,dx
        add si,SCREEN_WIDTH
        mov dword ptr es:[si],7FFFFF7Fh
        mov dword ptr es:[si+SCREEN_WIDTH],0FFFFFFFFh
        mov dword ptr es:[si+SCREEN_WIDTH*2],0FFFFFFFFh
        mov dword ptr es:[si+SCREEN_WIDTH*3],7FFFFF7Fh
        fld st;time
        fld st
        fld st
        fld st
        fmul const2_5
        fadd const1_5
        fsin
        fmul const120
        fistp xd        
        add xd,160;xd=sin(time*2.5+1.5)*120+160
        fmul const1_5
        fadd const2_5
        fcos
        fmul const60
        fistp yd        
        add yd,100;yd=cos(time*1.5+2.5)*60+100
        imul si,word ptr yd,SCREEN_WIDTH
        add si,word ptr xd
        mov word ptr es:[si+SCREEN_WIDTH], 0FFFFh
        mov word ptr es:[si+SCREEN_WIDTH*2], 0FFFFh
        fmul    const1_5
        fadd    const2_5
        fsin
        fmul    const120
        fistp   xd      
        add xd, 160;xd=sin(time*1.5+2.5)*120+160
        fmul    const2_5
        fadd    const1_5
        fcos
        fmul    const60
        fistp   yd      
        add yd,100;yd=cos(time*2.5+1.5)*60+100
        imul    si,word ptr yd,SCREEN_WIDTH
        add si,word ptr xd
        mov word ptr es:[si+SCREEN_WIDTH], 0FFFFh
        mov word ptr es:[si+SCREEN_WIDTH*2], 0FFFFh
        fadd    const003;time=+0.03
;waterscreen---------------------------------------------------
@@:     mov gs,buf1_ptr
        mov fs,buf2_ptr
        mov di, SCREEN_WIDTH+1
        mov cx,64000-321
loc_10440:  movzx   ax, byte ptr gs:[di-SCREEN_WIDTH-1]
        add al, byte ptr gs:[di-SCREEN_WIDTH]
        adc ah, 0
        add al, byte ptr gs:[di-SCREEN_WIDTH+1]
        adc ah, 0
        add al, byte ptr gs:[di-1]
        adc ah, 0
        add al, byte ptr gs:[di+1]
        add ah, 0
        add al, byte ptr gs:[di+SCREEN_WIDTH-1]
        adc ah, 0
        add al, byte ptr gs:[di+SCREEN_WIDTH]
        adc ah, 0
        add al, byte ptr gs:[di+SCREEN_WIDTH+1]
        adc ah, 0
        shr ax, 2
        sub al, byte ptr fs:[di]
        sbb ah, 0
        test    ax, ax
        jns @f
        xor ax, ax
@@:     mov fs:[di], al
        shr ax,2
        imul    si,ax,SCREEN_WIDTH+1
        add si, di;si=ax*321+di
        cmp si,SCREENSIZE-1
        jbe @f
        mov si,SCREENSIZE-1
@@:     mov es,pics
        lods byte ptr es:[si]
        mov es,screen_end
        stosb
        loop loc_10440
        mov ax, buf1_ptr
        xchg    ax, buf2_ptr
        mov buf1_ptr, ax
;copyvirtualscreen------------------------------------
        mov dx,IMPUT_STATUS_0
WaitVerticalSync:in al, dx
        test al, 8
        jz WaitVerticalSync
WaitNotVerticalSync:in al, dx
        test al, 8
        jnz WaitNotVerticalSync
        mov cx,SCREENSIZE/4
        xor si,si
        push ds
        push es
        pop ds;ds=screen_end
        xor di,di
        push VGA_SEGMENT
        pop es
        rep movsd
        mov ds,cx;ds=0
        mov ax,ds:[41Ah]
        sub ax,ds:[41Ch]
        pop ds
        jz main
exit:       mov ax,3
        int 10h     ; - VIDEO - SET VIDEO MODE
        int 20h
 
filename    db 'aqua11.bmp',0
const1_5    dd 1.5
const2_5    dd 2.5
const60     dd 60.0
const120    dd 120.0
const003    dd 0.03
end start
результат
4
Mikl___
Автор FAQ
11376 / 5919 / 535
Регистрация: 11.11.2010
Сообщений: 10,937
25.06.2013, 15:16  [ТС] #23
Графика DOS. Падающий снег в 3D
Размер COM-файла 187 байт. Во вложении исходный текст и СОМ-файл
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
; masm dos com #
.model tiny
.code
.386
org 100h
maxpix  equ 5000
WIDTH_SCREEN    equ 320
HEIGHT_SCREEN   equ 200
SCREENSIZE  equ WIDTH_SCREEN*HEIGHT_SCREEN
IMPUT_STATUS_0  equ 3DAh    ;регистр статуса ввода 0
VGA_SEGMENT equ 0A000h
ftmp    equ dword ptr [bp-4]
tmp equ word ptr [bp-6]
c_  equ byte ptr [bp-6-maxpix]
s   equ word ptr [bp-6-3*maxpix]
 
start:  enter   6+3*maxpix,0
    mov ax,13h
    int 10h     ; - VIDEO - SET VIDEO MODE
    mov ax,cs
    add ax,1000h
    mov gs,ax
    finit
; Инициализация снега
    mov si,maxpix
    xor ax,ax
@@: xor ax,bx          ;Random by Alexander Matchugovsky (2:5020/996.21)
    add ax,ax
    adc bx,0
    xor bx,ax
    mov s[si],ax
    shr al,4
    inc ax
    mov c_[si],al
    dec si
    jnz @b
    fldz    ;stf=0
mainloop:push gs
    pop es;es=gs
    xor di,di
    xor eax,eax
    mov cx,SCREENSIZE/4
    rep stosd
    mov si,maxpix
@@: fld st; fld stf
    fsin
    movzx eax,byte ptr c_[si];ftmp=c[SI]*5;
    imul eax,5
    mov ftmp,eax
    fmul ftmp
    fistp tmp
    mov ax, s[si]
    add tmp, ax      ;tmp+=s[SI];DI=tmp;
    mov di, tmp
    mov al, c_[si]
    add al,15        ;AL=c[SI]+15;
    stosb  ;GS:[DI]=AL;
    xor ax,ax
    mov al,c_[si]
    add ax,16
    shr ax,3
    imul ax,WIDTH_SCREEN
    add s[si], ax   ;s[SI]+=((c[SI]+16)/8)*320;
    sub si,2
    jnz @b
    fadd const005  ;stf+=0.05
    mov dx,IMPUT_STATUS_0; тормозим вывод на экран до следующего кадра
WaitVerticalSync:in al,dx
    test al,8
    jz WaitVerticalSync
WaitNotVerticalSync:in al,dx
    test al,8
    jnz WaitNotVerticalSync
    push VGA_SEGMENT
    pop es
    xor di,di
    mov cx,SCREENSIZE/4
    rep movs dword ptr es:[di],gs:[si]
    mov es,cx
    mov ax,es:[41Ah]
    sub ax,es:[41Ch]; было ли нажатие на клавиатуру?
    jz mainloop
    mov ax,3;восстанавливаем текстовый режим
    int 10h
    int 20h; выход из программы
const005    dd 0.05
end start
3
6a6kin
231 / 101 / 6
Регистрация: 18.04.2010
Сообщений: 294
16.07.2013, 00:20 #24
Анимация в DOS. Часть первая.
В данной статье рассмотрим создание простой анимации в графическом режиме 13H. Использовать будем TASM. Результатом будет небольшая COM-программа.
Подробно про графические режимы в DOS и рисование написал Mikl___ здесь: http://www.cyberforum.ru/post4641789.html
В данной статье рисовать будем напрямую в видеопамять, так как это удобнее и быстрее.
Что вообще мы будем рисовать? Как видно из картинки — это развевающийся флаг Белоруссии на однородном фоне с надписью вверху. Соотношение сторон у флага — 1:2. Программа завершается по нажатию клавиши Enter.

Самый простой способ нарисовать развевающийся флаг — рисовать его вертикальными полосами, координата Y которых зависит от времени и координаты X. Формулу лучше всего проверить, написав пример на каком-нибудь высокоуровневом языке(C, Wolfram Mathematica, Delphi). Так можно быстро протестировать формулу и получить нужный результат.

Я для начала написал простую программу с использованием Delphi 7. Формула вышла следующей:
Delphi
1
Y := 10*sin(i/20+t)*i/(H*2)-i/5;
где i — текущая X координата полоски.


Как стало ясно из вышеприведённой статьи (я надеюсь), чтобы нарисовать пиксель на экране в режиме 13H, нужно загрузить цвет пикселя (1 байт) по определённому адресу в массив пикселей. Адрес вычисляется по просто форумуле:
Код
адрес = 0A000h + X + Y*320
где X, Y — координаты точки на экране (320x200)

Сразу следует оговориться: анимация нужна плавная, без мерцаний, поэтому необходимо использовать пару простых приёмов: двойная буферизация и вертикальная синхронизация.

Двойная буферизация — принцип, согласно которому части изображения записываются не сразу в видеопамять, а сначала в промежуточный буфер (обычный массив), который потом сразу копируется в видеопамять. Если рисование занимает некоторое время, то изображение на экране может прорисовываться не сразу, а частями, т.к. данные из видеопамяти считываются быстрее, чем алгоритм рисования заполнит видеопамять новыми данными. Двойная буферизация сокращает время заполнения видеопамяти до времени копирования массива.

Вертикальной синхронизация — принцип, согласно которому заполнение видеопамяти происходит в специальный момент времени, в который данные из видеопамяти не считываются. Раньше в кинескопах поток электронов (луч), подсвечивал специальное вещество (люминофор), нанесенное на стекло. Если рассказывать примитивно, то луч построчно перемещался от начала дисплея до его конца и потом необходимо было его установить опять в начало. Для этого необходимо было некоторое время, которое называли обратным ходом луча. Пока луч шел обратно, его необходимо было гасить, для чего передавались специальные гасящие сигналы. Существует два вида таких сигналов: от конца строки до начала (горизонтальный или строчный импульс) и от конца кадра до его начала (вертикальный или кадровый импульс). Пока луч был погашен и перемещался в начало дисплея, можно было записать необходимую информацию в видеопамять, так как отображаться промежуточная информация не будет. Хотя в современных мониторах отсутствует именно такая система, но название осталось.

Сочетание этих двух принципов позволяет получить достаточно плавную анимацию, без мерцаний.

Обычно для рисования анимации применяется следующий подход: в цикле последовательно выполняются следующие действия:
  • очистка экрана
  • рисование кадра анимации
  • задержка на время показа кадра
  • изменение параметра времени (переменная T)
При использовании вышеприведённых методов (двойная буферизация и вертикальная синхронизация) добавляется ещё и ожидание обратного хода луча и последующее копирование буфера в видеопамять.

Так как требуется завершать программу по нажатию Enter, нужно также добавить проверку нажатия клавиши, и если нажат Enter, выходить из цикла. Для этого есть две функции прерывания 16H:
Код
AH = 0
	Ждёт нажатия клавиши
	На выходе в AX содержится код клавиши (AL = ASCII или 0; 
	AH = сканкод или расширенный ASCII)
AH = 1
	Проверяет было ли нажатие (но не ожидает его)
	На выходе ZF = 1, если клавиша не нажата, ZF = 0, 
	если была нажата и в AX то же, что и в 0 функции
Сформируем главную часть программы (после останется добавить лишь вспомогательные функции)
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
.MODEL TINY
.CODE
.386
ORG 100H
 
X = 40        ;начальная X-координата левого угла флага
Y = 80        ;начальная Y-координата левого угла флага
H = 90        ;ширина флага
W = 2*H       ;длина флага
BACK EQU 9    ;цвет фона (9 — светлосиний)
 
MAIN PROC
LOCALS @@
    ;инициализируем ES адресом начала видеопамяти
    PUSH 0A000h
    POP     ES
    ;устанавливаем видеорежим
    ;за это отвечает функция 0 прерывания 10h
    ;AH = 00H
    ;AL = <номер режима>
    MOV     AX, 13h
    INT     10h
    ;функция рисует заголовок и заливает его фоном
    CALL    PAINT_TITLE
@@CYCL:
    ;функция заполняет буфер пикселями цвета фона
    CALL    CLR_BUFF
    ;функция рисует сам флаг
    CALL    DRAW_FLAG
    ;функция ожидает обратного хода луча и копирует буфер в видеопамять
    CALL    COPY_BUFF
    ;функция выполняет задержку в 40 мс
    CALL    DELAY
    ;наращивается переменная времени
    INC     T
    ;проверяем была ли нажата клавиша (но не ждем нажатия)
    ;функция 1 прерывания 16h сбросит флаг ZF, если клавиша была нажата
    ;если клавиша была нажата, то информация о нажатии сохранится
    MOV     AH, 1
    INT     16h
    ;если клавиша не была нажата, продолжаем рисовать анимацию
    JZ      @@CYCL
    ;если всё-таки нажата, информацию о нажатии надо удалить и 
    ;проверить что за клавиша нажата
    ;функция 1 прерывания 16h удалит информацию и
    ;в AX вернет информацию о клавише
    MOV     AH,0
    INT     16h
    ;если нажат не Enter (код — 13 или 0DH)
    CMP     AL, 13
    ;то продолжаем рисовать
    JNE     @@CYCL
    ;устанавливаем текстовый видеорежим
    MOV     AX, 3
    INT     10h
    ;завершаем программу
    MOV     AH, 4Ch
    INT     21h
MAIN ENDP
 
TEXT          DB '6a6kin, cyberforum.ru','$' 
T             DW 0
TMP           DW 0
BUFF          DB 64000 DUP (?)
END MAIN
Если закомментировать все вызовы функций и скомпилировать программу (пример для файла с именем graph.asm):
Bash
1
2
tasm graph
tlink /t graph
то при запуске будет виден чёрный экран и программа завершиться по нажатию Enter.

Так как необходимо нарисовать ещё и надпись вверху экрана, то придется опять прибегнуть к хитростям
Нарисовать текст может всё та же функция 9 прерывания 21h, но постоянно её вызывать — издержки, которых можно избежать, так как текст не изменяется со временем и ещё записывается сразу в видеопамять. Ещё одна проблема — текст рисуется белыми буквами на чёрном фоне, поэтому фон нужно будет заменить.

Функцию рисования заголовка нужно вызвать всего один раз, полоса текста занимает высоту всего 8 пикселей, однако при копировании буфера в видеопамять, первые восемь строк копироваться не будут, чтобы не перезаписать текст.

Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PAINT_TITLE PROC
LOCALS @@
    PUSH    SI
    ;выводим текст с помощью 9 функции
    MOV     AH, 9
    MOV     DX, OFFSET TEXT
    INT     21H
    ;далее в цикле для первых восьми строк меняем
    ;все пиксели черного цвета (код — 0) на цвет фона
    MOV     SI, -1
@@CYCL:
    INC     SI
    CMP     SI, 320*8
    JAE     @@END
    CMP     BYTE PTR ES:[SI], 0
    JNE     @@CYCL
    MOV     BYTE PTR ES:[SI], BACK
    JMP     @@CYCL
@@END:  POP     SI
    RET 
PAINT_TITLE ENDP
Если добавить данную функцию в приведенную выше программу, то при запуске в самом верху будет текст на заданном фоне.

Рассмотрим также другие вспомогательные функции: DELAY, CLR_BUFF, COPY_BUFF

Функция задержки очень проста — это вызов функции 86h прерывания 15h
В CX : DX располагается количество микросекунд, на которое нужно остановить программу (прерывание запускает цикл на это время)
Ничего сложного в ней нет.
Assembler
1
2
3
4
5
6
7
DELAY PROC
    XOR     CX, CX
    MOV     DX, 40000
    MOV     AH, 86h
    INT     15h
    RET
DELAY ENDP
Очистка буфера — просто заполнение буфера пикселями фона. Для этого используется команда STOSB с префиксом REP.
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CLR_BUFF PROC
    PUSH    DI
    PUSH    ES
    ;так как для STOSB адрес должен быть в ES:DI,
    ;то необходимо запомнить ES и записать в него DS
    PUSH    DS
    POP     ES
    MOV     DI, OFFSET BUFF
    MOV     CX, 320*100
    MOV     AX, BACK*101h
    REP     STOSW
    POP     ES
    POP     DI
    RET
CLR_BUFF ENDP
Функция COPY_BUFF выполняет сразу две задачи:
  • ожидает обратный ход луча
  • копирует буфер в видеопамять
Нужно кое-что пояснить: во-первых, копировать нужно буфер без первых 8 строк, так как там заголовок. Там всё просто реализуется командой MOVSB. Во-вторых, это как происходит ожидание. Чтобы получить информацию о текущем состоянии луча, нужно считать байт из порта 03DAh, в котором четвёртый бит — состояние луча.
Всего в данной функции два цикла. Проверяет, идет ли на данный момент этот несуществующий луч обратно, и если он идет, то необходимо дождаться его завершения. Это необходимо, так как возможно луч уже почти дошел до начала и начнет рисоваться кадр и буфер скопироваться не успеет (представьте, что лыжник поднимается на подъёмнике, чтобы потом спуститься с холма, с подножья холма можно узнать только занят ли сейчас подъёмник или нет, а кто-то решил очистить холм от препятствий (и успеет пока лыжник поднимается от начала) — для этого ему нужно дождаться пока лыжник поднимется до вершины на подъёмнике (потому что он не знает где лыжник на данный момент находится), съедет с горы и опять начнет подъём).
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
COPY_BUFF PROC
LOCALS @@
    PUSH    SI
    PUSH    DI
    MOV     DX, 3DAh
@@CHK1: IN      AL, DX    ;считываем необходимый байт
    TEST    AL, 1000b ;проверяем на обратный ход луча
    JNZ     @@CHK1    ;ждём, если если луч идёт обратно
                 ;(лыжник где-то на подъёмнике, но непонятно где)
@@CHK2: IN      AL, DX
    TEST    AL, 1000b
    ;то же, только ждём по рисуется изображение (лыжник спускается с холма)
    JZ      @@CHK2
    MOV     SI, OFFSET BUFF 
    MOV     DI, 320*8
    MOV     CX, 320*192/2
    REP     MOVSW     ;копируем буфер
    POP     DI
    POP     SI
    RET
COPY_BUFF ENDP
Добавив эти функции, расскомментируйте их вызовы. При запуске весь экран будет залит фоновым цветом, а сверху будет виден заголовок.

Осталось самое главное — рисование самого флага. Для этого реализуем несколько дополнительных функций:
  • рисование вертикальной линии заданного цвета, длины и положения
  • вычисление положения вертикальной линии (координаты Y по необходимой формуле)
Как рисовать линию есть в посте Mikl___, я лишь сделал функцию, принимающую в качестве параметров свойства линии
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
;VOID DRAW_VERTICAL_LINE(WORD X, WORD Y, WORD HEIGHT, WORD COLOR)
DRAW_VERTICAL_LINE PROC
LOCALS @@
    PUSH    BP
    MOV     BP, SP
    PUSH    SI
 
    ;вычисляем индекс в массиве SI = X + Y * 320
    IMUL     SI,WORD PTR [BP+6],320
    ADD     SI, [BP+4]
    MOV     CX, [BP+8]  ;высота линии
    MOV     AL, [BP+10] ;цвет линии
@@CYCL:
    MOV     BYTE PTR BUFF[SI], AL
    ADD     SI, 320
    LOOP     @@CYCL
    POP     SI
    POP     BP
    RET
DRAW_VERTICAL_LINE ENDP
В этой функции выполняются необходимые арифметические операции (надо пояснить, что от i - текущей X-координаты полосы я отнимаю X-координату начального положения, чтобы сместить центр координат в начало левого верхнего угла флага).
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
;WORD GET_Y(WORD i, WORD Yorig)
GET_Y PROC
    PUSH    BP
    MOV     BP, SP
 
    SUB     WORD PTR [BP+4], X
    FINIT
    FILD    WORD PTR [BP+4] ;ST(0) = i - X0
    MOV     TMP, 20
    FIDIV   TMP             ;ST(0) = (i - X0)/20
    FIADD   T               ;ST(0) = (i - X0)/20 + T
    FSIN                    ;ST(0) = sin((i - X0)/20 + T)
    FIMUL   WORD PTR [BP+4] ;ST(0) = sin((i - X0)/20 + T)*i
    MOV     TMP, 2*H
    FIDIV   TMP             ;ST(0) = sin((i - X0)/20 + T)*i/(2*H)
    MOV     TMP, 10
    FIMUL   TMP             ;ST(0) = 10*sin((i - X0)/20 + T)*i/(2*H)
    FIST    TMP             ;TMP = 10*sin((i - X0)/20 + T)*i/(2*H)
    MOV     DX, TMP
    ADD     DX, [BP+6]      ;DX = Yorig + 10*sin((i - X0)/20 + T)*i/(2*H)
    MOV     AX, [BP+4]
    MOV     BL, 5
    DIV     BL
    MOV     AH, 0           ;AX = i/5
    SUB     DX, AX
    MOV     AX, DX          ;AX = Yorig + 10*sin((i - X0)/20 + T)*i/(2*H) - i/5
    POP     BP
    RET
GET_Y ENDP
Дело за малым — функция рисования самого флага. Тут всё просто — в цикле рисуем по две полосы каждого цвета, вызывая написанные ранее вспомогательные функции.
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
DRAW_FLAG PROC
LOCALS @@
    PUSH    SI
    PUSH    DI
 
    ;в SI — X-координата полосы
    ;в DI — Y-координата полосы
    MOV     SI, X
@@CYCL:
    MOV     DI, Y
 
    ;формируем окончательный вариант Y-координаты
    PUSH    DI
    PUSH    SI
    CALL    GET_Y
    ADD     SP, 4
 
    MOV     DI, AX
 
    ;рисуем верхнюю красную линию
    PUSH    12    ;цвет — светлокрасный
    PUSH    2*H/3 ;высота — две-трети от высоты всего флага
    PUSH    DI
    PUSH    SI
    CALL    DRAW_VERTICAL_LINE
    ADD     SP, 8
 
    ;рисуем нижнюю зелёную линию
    PUSH    10    ;цвет — светлозелёный
    PUSH    H/3   ;высота — одна-треть от высоты флага
    MOV     AX, DI
    ADD     AX, 2*H/3 
    PUSH    AX    ;смещаем положение по вертикали на две трети от высоты флага
    PUSH    SI
    CALL    DRAW_VERTICAL_LINE
    ADD     SP, 8
 
    ;и так для каждой полосы по всей длине флага
    INC     SI
    CMP     SI, X+W
    JB      @@CYCL
 
    POP     DI
    POP     SI
    RET
DRAW_FLAG ENDP
Остается только раскомментировать вызовы всех функций и добавить недостающие. Компилируете и запускаете:
Bash
1
2
3
tasm graph
tlink /t graph
graph
Полный код и сом-файл — здесь
В репозитории находится также makefile. Для полной компиляции достаточно написать:
Bash
1
make
5
6a6kin
231 / 101 / 6
Регистрация: 18.04.2010
Сообщений: 294
16.07.2013, 09:47 #25
Анимация в DOS. Часть вторая.
В продолжение предыдущей статьи про анимацию, в этой статье мы рассмотрим другой вид анимации в режиме 13h. Программа опять будет написана под TASM и представлять из себя COM-файл.

Особенность данной анимации в том, что видеопамять заполняется лишь единожды. Соответственно, ни двойная буферизация, ни вертикальная синхронизация не нужны, так как информация больше не будет меняться. Изображением будет состоять из последовательных элементов (например, линий), но полностью разных цветов. Меняться будет палитра цветов, в которой находится информация о цвете каждого кода (код тех байтов, которые заносятся в видеопамять). Достаточно в цикле сдвигать цвета в палитре, чтобы создать простую и красивую анимацию.

Основная часть программы практически такая же, как и в предыдущей статье, только убраны некоторые ненужные части и изменены функции, управляющие анимацией.
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
.MODEL TINY
.CODE
ORG 100H
 
MAIN PROC
LOCALS @@
    ;инициализируем ES адресом начала сегмента видеопамяти
    MOV     AX, 0A000h
    MOV     ES, AX
    ;устанавливаем нужный видеорежим
    MOV     AX, 13h
    INT     10h
;функция, отвечающая за начальное наполнение видеопамяти изображением
    CALL    DRAW_SQUARES
@@CYCL:
    ;функция, меняющая палитру (что автоматически меняет вид изображения на экране)
    CALL    SHIFT_PALETTE
    ;задержка на время показа кадра
    CALL    DELAY
    ;меняет переменную времени
    INC     T
    ;далее те же проверки на нажатие клавиши Enter
    MOV     AH,1
    INT     16h
    JZ      @@CYCL
    MOV     AH,0
    INT     16h
    CMP     AL,13
    JNE     @@CYCL
    ;устанавливаем текстовый видеорежим
    MOV     AX,3
    INT     10h
    ;завершаем программу
    MOV     AH,4Ch
    INT     21h
MAIN ENDP
 
DELAY PROC
    XOR     CX,CX
    MOV     DX,40000
    MOV     AH,86h
    INT     15h
    RET
DELAY ENDP
 
T DB 0 ;переменная времени
 
END MAIN
Как видно из изображения вверху поста, оно состоит из прямоугольников, вложенных друг в друга. Для их рисования понадобятся две функции:
  • нарисовать горизонтальную прямую
  • нарисовать вертикальную прямую
Вертикальная прямая рисуется так же, как и в предыдущей статье, только сразу в видеопамять.
Горизонтальная прямая легко рисуется используя STOSB, так как пиксели располагаются в памяти один за одним.
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
;VOID DRAW_VERTICAL_LINE(WORD X, WORD Y, WORD HEIGHT, WORD COLOR)
DRAW_VERTICAL_LINE PROC
LOCALS @@
    PUSH    BP
    MOV     BP, SP
    PUSH    SI
 
    ;вычисляем индекс в массиве SI = X + Y * 320
    MOV     AX, 320
    MUL     WORD PTR [BP+6]
    MOV     SI, AX
    ADD     SI, [BP+4]
 
    MOV     CX, [BP+8]  ;высота линии
    MOV     AL, [BP+10] ;цвет линии
@@CYCL:
    MOV     BYTE PTR ES:[SI], AL
    ADD     SI, 320
    DEC     CX
    JNZ     @@CYCL
 
    POP     SI
    POP     BP
    RET
DRAW_VERTICAL_LINE ENDP
 
;VOID DRAW_HORIZONTAL_LINE(WORD X, WORD Y, WORD WIDTH, WORD COLOR)
DRAW_HORIZONTAL_LINE PROC
    PUSH    BP
    MOV     BP, SP
    PUSH    DI
 
    ;вычисляем индекс в массиве SI = X + Y * 320
    MOV     AX, 320
    MUL     WORD PTR [BP+6]
    MOV     DI, AX
    ADD     DI, [BP+4]
 
    MOV     CX, [BP+8]  ;длина линии
    MOV     AL, [BP+10] ;цвет линии
    REP     STOSB
 
    POP     DI
    POP     BP
    RET
DRAW_HORIZONTAL_LINE ENDP
Можно приступать к рисованию вложенных прямоугольников. Высота экрана — 200 пикселей, прямоугольников будет в два раза меньше, так как есть два горизонтальных ребра по одному пикселю. Ещё не будем рисовать самый внутренний прямоугольник, так как он нулевой высоты. Соответственно, 99 раз будем вызывать четыре раза функции рисования линий в цикле. Цвет линий будет просто равен текущему индексу, что нам и нужно.

Нужно заметить, что рисование вертикальных линий идёт с опережением, так как их высота меньше и Y-координата меньше.

Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
DRAW_SQUARES PROC
LOCALS @@
    PUSH    SI
    MOV     SI, 0
 
@@CYCL:
    ;рисуем верхнюю горизонтальную черту
    PUSH    SI        ;помещаем в стек первый параметр — цвет, равен индексу
    MOV     AX, 320
    SUB     AX, SI
    SUB     AX, SI    ;вычисляем длину линии, в зависимости от того,
    PUSH    AX        ;какой по счёту прямоугольник
    PUSH    SI        ;начальные координаты равны счётчику
    PUSH    SI
    CALL    DRAW_HORIZONTAL_LINE
    ADD     SP, 4     ;удаляем из стека только первые два параметра
 
    ;рисуем нижнюю горизонтальную черту
    MOV     AX, 199
    SUB     AX, SI    ;вычисляем изменённые начальные координаты для нижней линии
    PUSH    AX
    PUSH    SI
    CALL    DRAW_HORIZONTAL_LINE
    ADD     SP, 8     ;удаляем все параметры из стека
 
    ;рисуем левую вертикальную черту
    INC     SI        ;увеличиваем счётчик
    PUSH    SI        ;вертикальные линии рисуются для следующего прямоугольника
    MOV     AX, 200   ;и строятся по принципу горизонтальных линий
    SUB     AX, SI
    SUB     AX, SI
    PUSH    AX
    PUSH    SI
    PUSH    SI
    CALL    DRAW_VERTICAL_LINE
    ADD     SP, 4
 
    PUSH    SI
    MOV     AX, 319
    SUB     AX, SI
    PUSH    AX
    CALL    DRAW_VERTICAL_LINE
    ADD     SP, 8
 
    CMP     SI, 99   ;рисуем только 99 штук
    JB      @@CYCL
 
    POP     SI
    RET
DRAW_SQUARES ENDP
Осталась самая главная функция, которая непосредственно изменяет палитру. Для изменения цветов в палитре есть специальные порты — 3C8h и 3C9h. В первый порт нужно занести номер цвета в палитре, который мы хотим поменять. Во второй нужно по очереди занести три цветовые компоненты RGB. Значащими в байте являются первые 6 бит, то есть возможны 64 значения одной компоненты. Для нашей анимации их я просто подобрал алгоритм, чтобы достойно выглядело.
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
SHIFT_PALETTE PROC
LOCALS @@
    MOV     CL, 1      ;нулевой всегда будет чёрным
 
@@CYCL:
    MOV     AL, CL     ;заносим номер цвета
    MOV     DX, 03C8h
    OUT     DX, AL
 
    MOV     AL, T      ;в AL будет значение цветовой компоненты
    INC     DX         ;увеличиваем значение порта
 
    ADD     AL, CL     ; некоторое преобразования для эффекта
    OUT     DX, AL     ;заносим красную компоненту
 
    ADD     AL, CL
    OUT     DX, AL     ;заносим зелёную компоненту
 
    ADD     AL, CL
    OUT     DX, AL     ;заносим синюю компоненту
 
    INC     CL
    CMP     CL, 100
    JB      @@CYCL
 
    RET
SHIFT_PALETTE ENDP
Нужно добавить все написанные функции и можно компилировать и запускать:
Bash
1
2
3
tasm graph
tlink /t graph
graph
Результат:
Полный код и сом-файл — здесь
В репозитории находится также makefile. Для полной компиляции достаточно написать:
Bash
1
make
P.S. Процедуру SHIFT_PALETTE можно улучшить и по скорости и по размеру
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
SHIFT_PALETTE PROC
    MOV     CX, 99      ;номер последнего цвета 
@@: MOV     DX, 3C8h
    MOV     AL, CL     ;номер цвета
    MOV     AH,Red      ;значение красной компоненты
    OUT     DX, AX    
    INC     DX         ;увеличиваем значение порта  
    MOV     AL,Green
    OUT     DX, AL     ;заносим зелёную компоненту 
    MOV     AL, Blue
    OUT     DX, AL     ;заносим синюю компоненту 
    loop      @b
    RET
SHIFT_PALETTE ENDP
2
Mikl___
Автор FAQ
11376 / 5919 / 535
Регистрация: 11.11.2010
Сообщений: 10,937
20.08.2013, 17:17  [ТС] #26
Графика DOS. Эффект дождь
Размер COM-файла 701 байт. Во вложении исходный текст, картинка в формате PCX и COM-файл
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
; masm dos com #
.model tiny
.code
.686
org 100h
PALSIZE     equ 256*3
WIDTH_SCREEN    equ 320
HEIGHT_SCREEN   equ 200
SIZESCREEN  equ WIDTH_SCREEN*HEIGHT_SCREEN
VGA_SEGMENT equ 0A000h
PCXBUFSIZE  equ 400h
 
PCX_HEADER struct 
  PCX_manufacturer  db ?;        manufacturer byte (always 0A0h)
  PCX_version       db ?;        pcx Version
  PCX_encoding      db ?;        (always 1)
  PCX_bits_per_pixel    db ?;        color bits per pixel
  PCX_xmin      dw ?;        image origin x
  PCX_ymin      dw ?;        image origin y
  PCX_xmax      dw ?;        image end x
  PCX_ymax      dw ?;        image end y
  PCX_hres      dw ?;        horizontal resolution
  PCX_vres      dw ?;        vertical resolution
  PCX_palette1      db 48 dup(?);(color palette, for older not 256 col vers.)
  PCX_reserved      db ?;        reserved byte
  PCX_color_planes  db ?;        number of color planes
  PCX_bytes_per_line    dw ?;        line buffer size
  PCX_palette_type  dw ?;        grey or color palette indicator
  PCX_reserved2     db 58 dup(?);reserved
PCX_HEADER ends
 
start   proc    
local xcount:word
local ycount:word
local local_bytesperline:word
local readbuf[400h]:byte
local hpcx:PCX_HEADER
local temp:dword
local buf3_seg:word
local LineTable[HEIGHT_SCREEN]:word
 
        mov ax, 13h
        int 10h
        mov dx, 3C4h; индексный регистр последовательного контроллера
        mov al, 4;MEMORY MODE; индекс регистра режима выбора памяти
        out dx, al  
        inc dx
        in  al, dx  
        and al, 0F7h;выключим адресацию по 4 байта
        or  al, 4   ;выключим чет/нечет
        out dx, al  
        mov dx, 3CEh; индексный регистр графического контроллера
        mov al, 5;GRAPHICS MODE ;индекс регистра графического режима
        out dx, al
        inc dx
        in  al, dx      
        and al, 0EFh;выключим чет/нечет
        out dx, al      
        dec dx
        mov al, 6;MISCELLANEOUS;индекс дополнительного регистра
        out dx, al      
        inc dx
        in  al, dx      
        and al, 0FDh;выключим последовательности
        out dx, al      
;разрешим запись во все плоскости, чтобы мы могли очистить по 4 плоскости за один раз
        mov dx, 3C4h; индексный регистр последовательного контроллера
        mov ax,0F02h;2 - индекс регистра маски карты, 0F - 4 плоскости
        out dx,ax; al       
        push    VGA_SEGMENT
        pop es
        xor di, di
        xor ax, ax
        mov cx, 8000h; число слов в 64 Кбайт
        rep stosw; чистим видеопамять
;переключение в режим 320х400 256 цветов не сканируя каждую линию дважды
        mov dx, 3D4h;индексный регистр КЭЛТ
        mov al, 9;MAX_SCAN_LINE;индекс регистра максимальной линии сканирования в КЭЛТ
        out dx, al      
        inc dx
        in  al, dx      
        and al, 0E0h;установим максимальную линию сканирования = 0
        out dx, al      
        dec dx
;изменим сканирование КЭЛТ и перейдем от 4-байтового режима в 1-байтовый, 
;позволив КЭЛТ сканировать более 64 Кбайт видеопамяти
        mov al, 14h;UNDERLINE;индекс регистра позиции подчеркивания в КЭЛТ
        out dx, al      
        inc dx
        in  al, dx      
        and al, 0BFh;выключим 4-байтовый режим
        out dx, al      
        dec dx
        mov al, 17h;MODE CONTROL;регистр управления режимом в КЭЛТ
        out dx, al      
        inc dx
        in  al, dx
        or  al, 40h;включим побайтовый режим, чтобы память 
;сканировалась как бы линейно, как в режимах 10h и 12h
        out dx, al
 
        mov ax, cs
        add ah, 10h
        mov gs,ax
        add ah, 10h
        mov fs,ax
        add ah, 10h
        mov buf3_seg, ax
;SetupLineTable
        push    ss
        pop es
        lea di,LineTable
        xor ax, ax
        mov cx, HEIGHT_SCREEN
@@:     stosw
        add ax, WIDTH_SCREEN
        loop    @b
;RANDOMIZE()
        rdtsc
        mov temp, eax
;CleanSegments
        xor eax, eax
        xor di, di
        push    gs
        pop es; es=buf1_seg
        mov cx, 8000h
        rep stosd       
;load PCX--------------------------------------------------------------
        mov dx,offset filename; OPEN DISK FILE ONLY FOR READ 
        mov ax,3D00h    ;WITH HANDLE
        int 21h     ; DS:DX -> ASCIZ filename
        mov bx, ax
        mov cx, 80h        ;sizeof(hpcx)
        lea dx,hpcx     ;#hpcx
        mov ah, 3Fh; READ FROM FILE WITH HANDLE
        int 21h; BX = file handle, CX = number of bytes to read, DS:DX -> buffer
        mov ax,hpcx.PCX_ymax
        sub ax,hpcx.PCX_ymin
        inc ax
        mov ycount, ax ;ycount=hpcx.PCX_ymax-hpcx.PCX_ymin+1
        mov ax,hpcx.PCX_xmax
        sub ax,hpcx.PCX_xmin
        inc ax
        mov local_bytesperline,ax;local_bytesperline=hpcx.PCX_xmax-hpcx.PCX_xmin+1
        push    fs
        pop     es  ;es=buf2_seg
 
        xor di, di
        mov si, PCXBUFSIZE;SI=PCXBUFSIZE
 
@0:     mov ax,local_bytesperline
        mov xcount,ax   ;xcount=local_bytesperline
 
@1:     cmp si, PCXBUFSIZE        ;IF(SI==PCXBUFSIZE)
        jnz @f
        mov cx,PCXBUFSIZE        
        lea dx,readbuf
        mov ah, 3Fh; READ FROM FILE WITH HANDLE
        int 21h; BX = file handle, CX = number of bytes to read, DS:DX -> buffer
 
        xor si, si
 
@@:     mov al,readbuf[si]
        inc si
        cmp al, 0C0h;IF(AL>=0xC0)
        jb  @f
        and al, 3Fh ;AL &= 0x3F
        xor cx, cx
        mov cl, al
        sub xcount,cx   ;xcount-=CX
        cmp si, PCXBUFSIZE  ;IF(SI==PCXBUFSIZE)
        jnz @2
        push    cx
        mov cx, PCXBUFSIZE
        lea dx,readbuf  ;#readbuf
        mov ah, 3Fh; READ FROM FILE WITH HANDLE, BX = file handle,
        int 21h; CX = number of bytes to read, DS:DX -> buffer
        pop cx
        xor si, si
 
@2:     mov al,readbuf[si]
        inc si
        rep     stosb
        jmp short @3
 
@@:     dec xcount
        stosb
 
@3:     cmp xcount,0
        ja  @1
        dec ycount
        jnz @0
        mov cx, -1
        mov dx,-(PALSIZE+1)
        mov ax, 4202h
        int 21h     ; MOVE FILE READ/WRITE POINTER (LSEEK)
                    ; AL = method: offset from end of file
        mov cx, PALSIZE+1
        lea dx,readbuf;#readbuf
        mov ah, 3Fh; READ FROM FILE WITH HANDLE
        int 21h; BX = file handle, CX = number of bytes to read, DS:DX -> buffer
        lea di,readbuf+1
        mov cx, PALSIZE
 
@@:     shr byte ptr [di], 2
        inc di
        loop    @b
;SETVGADAC(0,,768,,,#readbuf[1])
        xor ax, ax
        mov cx, PALSIZE
        lea si,readbuf+1
        mov dx, 3C8h
        out dx, al
        inc dx
        rep outsb
        mov ah, 3Eh; CLOSE A FILE WITH HANDLE
        int 21h     ; BX = file handle
;CHANGEPICTOR
        xor si, si
        xor dx, dx
        push    VGA_SEGMENT
        pop es
@4: xor di, di
@@:     push    dx
        mov cx, di
;WRITEPIXEL400
        push    di
        imul    di,dx,80
        push    cx
        shr cx, 2
        add di,cx
        pop cx
        and cl, 3
        mov ah, 1
        shl ah, cl
        mov dx, 3C4h; EGA: sequencer address reg
        mov al, 2; sequencer reset.
        out dx, ax; Bits of data 0-1 indicate asynchronous/synchronous reset.
        movs byte ptr es:[di],fs:[si]
        pop di
        pop dx
        inc di
        cmp di, WIDTH_SCREEN
        jb  @b
        inc dx
        cmp dx, HEIGHT_SCREEN
        jb  @4
 
        xor di, di
        mov dx, HEIGHT_SCREEN-1
@5:     xor si, si
@@:     push    dx
        mov cx, si
        push    si
        imul    si,dx,80
        push    cx
        shr cx, 2
        add si, cx
        pop cx
        and cl, 3
        mov ah, cl
        mov dx, 3CEh
        mov al, 4
        out dx, ax      ; EGA: graph 1 and 2 addr reg:
                    ; set/reset.
                    ; Data bits 0-3 select planes for write mode 00
        mov al, es:[si]
        pop si
        pop dx
        mov fs:[di],al
        inc     di
        inc si
        cmp si, WIDTH_SCREEN
        jb  @b
        dec dx
        jns short @5
mainloop:;SetRandomPoint
        mov ax,word ptr temp+2
        mov cx,word ptr temp
        shld    ax,cx,8
        shl     cx,8
        rcr ax, 1
        rcr cx, 1
        add word ptr temp, cx
        adc ax, word ptr temp+2
        add word ptr temp, 25321
        adc ax, 13849
        mov word ptr temp+2, ax
        xor dx, dx
        mov cx, SIZESCREEN
        div cx
        mov     bx, dx
        mov byte ptr gs:[bx], 7Fh
;DoWater
        mov es, buf3_seg
        xor ax, ax
        xor di, di;cx=SIZESCREEN
@6:     xor bx, bx
        lea si, gs:[di-WIDTH_SCREEN-1]
        lods    byte ptr gs:[si]
        add bx, ax
        lods    byte ptr gs:[si]
        add bx, ax
        lods    byte ptr gs:[si]
        add bx, ax
        add si, WIDTH_SCREEN-3
                lods    byte ptr gs:[si]
        add bx, ax
        lods    byte ptr gs:[si]
        add bx, ax
        lods    byte ptr gs:[si]
        add bx, ax
        add si, WIDTH_SCREEN-3
                lods    byte ptr gs:[si]
        add bx, ax
        lods    byte ptr gs:[si]
        add bx,ax
        lods    byte ptr gs:[si]
        add ax,bx
        add si,WIDTH_SCREEN-3
        sub al,gs:[di]
        sbb ah,0
        sar ax,2
        sub al,es:[di]
        jns @f
        xor ax,ax
@@:     stosb
                loop    @6
;DupeToScreen
        push    VGA_SEGMENT
        pop es
        xor si,si
        mov dx,HEIGHT_SCREEN
 
@7:     xor di,di
@@:     xor bx,bx
        mov bl,gs:[si]
        push    si
        mov ax,si
        shl bx,1
        mov si,bx
        add si,LineTable[si]
        add si,ax
        push    dx
        mov cx,di
;WRITEPIXEL400
        push    di
        imul    di,dx,80
        push    cx
        shr cx,2
        add di,cx
        pop cx
        and cl,3
        mov ah,1
        shl ah,cl
        mov dx,3C4h   ; EGA: sequencer address reg
        mov al,2      ; sequencer reset.
        out dx,ax   ; Bits of data 0-1 indicate asynchronous/synchronous reset.
        movs    byte ptr es:[di],fs:[si]
                pop di
        pop dx
        pop si
        inc si
        inc di
        cmp di,WIDTH_SCREEN
        jb  @b
        inc dx
        cmp dx,HEIGHT_SCREEN*2
        jb  @7
        push gs
        push buf3_seg
        pop gs  ; buf1_seg <--> buf3_seg
        pop buf3_seg
        mov ah, 1
        int 16h     ; KEYBOARD - CHECK BUFFER, DO NOT CLEAR
        jz  mainloop
        mov ax, 3
        int 10h     ; - VIDEO - SET VIDEO MODE
        int 20h
start   endp
filename db 'forest.pcx',0
end start
3
murderer
3314 / 1461 / 133
Регистрация: 06.10.2010
Сообщений: 3,215
01.09.2013, 09:37 #27
Заставка дождь.
Продолжаем разговор на тему "Эффект след от водомерки на поверхности воды". DOS - это конечно хорошо, но мы живём в эпоху AMD64, а не X86. В этой статье я опишу процесс создания программы-заставки под Windows с использованием инструкций SSE и FMA3, ну и конечно прикрутим шейдеры.

Программа заставка - это обычный PE с расширением scr. Но небольшие различия всё-таки есть
  1. В ресурсах программы должна находиться строка с названием заставки (это нужно для того, чтобы название отображалось не зависимо от имени файла).
  2. При запуске заставки Windows может передать один из трёх параметров:
    • /c - запуск диалогового окна с настройками заставки. Выход из этого режима происходит при закрытии диалогового окна.
    • /p - запуск предварительного просмотра. После этого параметра через пробел идёт хендл окна предпросмотра в десятичном представлении. Выход из этого режима происходит при скрытии окна предпросмотра (проверяем через IsWindowVisible).
    • /s - запуск полноэкранного просмотра. Выход из этого режима происходит при проявлении активности пользователя.


Я буду использовать тот же алгоритм, что описал Mikl___, но для получения более крупных волн карта высот будет в 16 раз меньше текстуры.

Генерация капли.

В связи с особенностями реализации капля не должна падать слишком близко к краям экрана (не ближе радиуса волны) иначе волна будет уходить за правый край и выходить с левого. Экспериментально я установил, что радиус волны приблизительно равен 50 пикселям. Проинициализируем регистры перед основным циклом.
Assembler
1
2
3
cvtpi2ps xmm7,qword[TexWidth]
cvtpi2ps xmm8,qword[TexWidth-4] ;xmm8 - {1; Width}
subps    xmm7,dqword[Border]    ;xmm7 - {Width-100; Height-100}
Теперь уже в цикле генерируем каплю. Координаты капли вычисляются так
Код
x = random mod (Width-100) + 50
y = random mod (Height-100) + 50
На самом деле не обязательно учитывать радиус волны при расчёте y-координаты - я это сделал для симметрии. Целью данного урока было попрактиковаться в программировании SSE/FMA, поэтому перепишем формулу для вещественных чисел.
Код
x = round(frac(random/(Width-100))*(Width-100)+50)
y = round(frac(random/(Height-100))*(Height-100)+50)
На первый взгляд может показаться, что формула очень усложнилась, но давайте посмотрим как это будет на ассемблере
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
rdtsc
xor    al,ah 
test   al,15
jne @f
    ;Генерируем случайные координаты
    shr         eax,1
    mov         [esp-8],eax
    mov         [esp-4],eax
    cvtpi2ps    xmm0,[esp-8]                 ;xmm0 = {           random,                                         random                               }
    divps       xmm0,xmm7                    ;xmm0 = {           random/(Width-100),                             random/(Height-100)                  }
    vfrczps     xmm0,xmm0                    ;xmm0 = {      frac(random/(Width-100)),                       frac(random/(Height-100))                 }
    vfmadd213ps xmm0,xmm7,dqword[HalfBorder] ;xmm0 = {      frac(random/(Width-100))*(Width-100)+50,        frac(random/(Height-100))*(Height-100)+50 }
    roundps     xmm0,xmm0,0                  ;xmm0 = {round(frac(random/(Width-100))*(Width-100)+50), round(frac(random/(Height-100))*(Height-100)+50)}
    ;Вычисляем адрес
    dpps        xmm0,xmm8,110001b            ;xmm0 = xmm0[1]*Width+xmm0[0]*1.0
    cvtss2si    eax,xmm0
    ;Бросаем каплю
    mov         word[rsi+rax],$FFFF
    add         eax,[TexWidth]
    mov         word[rsi+rax],$FFFF
@@:
В качестве ГПСЧ я использовал инструкцию rdtsc. Однако в новых процессорах Intel Broadwell появится инструкция rdrand для получения случайного (не псевдо) числа. Я не стал её использовать т.к. эмулятор Broadwell не работает с OpenGL, а мой процессор не поддерживает rdrand.

Размытие карты высот

Тут проще
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
mov    rcx,<размер карты высот - ширина*2>
center:movd    xmm6,[rdi+rcx+Width+1]     ;центральный пиксель [x,y]
    up:pinsrd  xmm4,[rsi+rcx-1],0         ;верхние 3 пикселя  {[x-1,y-1],[x,y-1],[x+1,y-1]}
  down:pinsrd  xmm4,[rsi+rcx+Width*2-1],1 ;нижние 3 пикселя   {[x-1,y+1],[x,y+1],[x+1,y+1]}
  left:pinsrb  xmm4,[rsi+rcx+Width],0     ;левый пиксель       [x-1,y]
 right:pinsrb  xmm4,[rsi+rcx+Width+2],4   ;правый пиксель      [x+1,y]
       psadbw  xmm4,xmm5                  ;xmm4 = <сумма 8 соседних пикселей>; xmm5 = 0
       psrld   xmm4,2                     ;xmm4 = xmm4/4
       psubusb xmm4,xmm6                  ;xmm4 = <сумма 8 соседних пикселей>/4-<центральный пиксель>
result:pextrb  [rdi+rcx+Width+1],xmm4,0   ;сохраняем результат
       dec     rcx
jns center
xchg   rsi,rdi ;меняем указатели местамих.
Однако ширина карты высот нам не известна, т.к. заставка универсальная и может запускаться на любом разрешении. Чтобы не добавлять в цикл новые инструкции - просто модифицируем код, подставив нужные значения вместо Width (смотри в аттаче).

Окончательный рендер

За расчёт смещений отвечает несложный шейдер
Assembler
1
2
3
4
5
6
7
8
9
10
11
!!ARBfp1.0
TEMP coord1,coord2,texcoord;
MUL  texcoord,fragment.position.xyxy,program.local[1].xyxy; ;вычисляем текстурные координаты texcoord={FragCoord.x/ScreenWidth,FragCoord.y/ScreenHeight}
TEX  coord1,texcoord,texture[1],2D;                         ;coord1   =HeightMap[y,x]
SUB  coord2,texcoord.xyxy,program.local[0];                 ;coord2   ={x-1,y,x,y-1}
TEX  coord2.x,coord2.xyxy,texture[1],2D;                    ;coord2.xy=HeightMap[y,x-1]
TEX  coord2.y,coord2.zwzw,texture[1],2D;                    ;coord2.zw=HeightMap[y-1,x]
SUB  coord1,coord1.xxxx,coord2;                             ;coord1   ={HeightMap[y,x]-HeightMap[y,x-1], HeightMap[y,x]-HeightMap[y-1,x]}
MAD  texcoord,coord1,{0.25,0.25,0.25,0.25},texcoord;        ;texcoord ={x+(HeightMap[y,x]-HeightMap[y,x-1])*0.25, y+(HeightMap[y,x]-HeightMap[y-1,x])*0.25}
TEX  result.color,texcoord,texture[0],2D;
END
Как видите шейдер даже не требует указания текстурных координат. В парметры передаём следующее
Код
program.local[0] = {1/Width, 0, 0, 1/Height}
program.local[1] = {1/ScreenWidth, 1/ScreenHeight}
Установка заставки осуществляется через контекстное меню.


ссылки:
Описание инструкций CPU
ARB Fragment Program
Соглашение fastcall x64
Использование регистров в fastcall x64
6
Mikl___
Автор FAQ
11376 / 5919 / 535
Регистрация: 11.11.2010
Сообщений: 10,937
03.09.2013, 04:14  [ТС] #28
Графика DOS - эффект "линза" + сталкивающиеся шары
Если линза на экране - окружность (x0,y0,r0), то в точках (x,y), для которых (x-x0)2+(y-y0)2http://www.cyberforum.ru/cgi-bin/latex.cgi?\leqr02,рисуется точка, которая, если бы линзы не было, изображалась бы в точке (x0+(x-x0)*k,y0+(y-y0)*k), где http://www.cyberforum.ru/cgi-bin/latex.cgi?k= \frac{const_{1}}{\sqrt{(x-x_{0})^{2}+(y-y_{0})^{2}}+ const_{2}}. Можно заранее рассчитать таблицу смещений [-r0..r0,-r0..r0].
Размер COM-файла 1030 байт. Исходный текст, СОМ-файл, картинка в формате pcx во вложении
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
; masm dos com #
.model tiny
.code
.686
org 100h
NUMBALL     equ 3
PCXBUFSIZE  equ 400h
PALSIZE     equ 256*3
WIDTH_SCREEN    equ 320
HEIGHT_SCREEN   equ 200
SIZESCREEN  equ WIDTH_SCREEN*HEIGHT_SCREEN
VGA_SEGMENT equ 0A000h
IMPUT_STATUS_0  equ 3DAh    ;регистр статуса ввода 0
 
BALLINFO struct 
    X dw ?;+0
    Y dw ?;+2
    R dw ?;+4
    xD dw ?;+6
    yD dw ?;+8
    Clinch dw ?;+10
BALLINFO ends
PCX_HEADER struct 
  PCX_manufacturer db ?; manufacturer byte (always 0A0h)
  PCX_version db ?; pcx Version
  PCX_encoding db ?;(always 1)
  PCX_bits_per_pixel db ?;     color bits per pixel
  PCX_xmin dw ?;           image origin x
  PCX_ymin dw ?;               image origin y
  PCX_xmax dw ?;               image end x
  PCX_ymax dw ?;               image end y
  PCX_hres dw ?;               horizontal resolution
  PCX_vres dw ?;               vertical resolution
  PCX_palette1 db 48 dup(?);   (color palette, for older not 256 col vers.)
  PCX_reserved db ?;           reserved byte
  PCX_color_planes db ?;       number of color planes
  PCX_bytes_per_line dw ?;     line buffer size
  PCX_palette_type dw ?;       grey or color palette indicator
  PCX_reserved2 db 58 dup(?);  reserved
PCX_HEADER ends
 
start   proc
local   z:dword
local   i:word
local   hpcx:PCX_HEADER
local   xcount:word
local   ycount:word
local   local_bytesperline:word
local   readbuf[400h]:byte
 
        mov ax, 13h
        int 10h     ; - VIDEO - SET VIDEO MODE
        mov ax, cs
        add ax, 1000h
        mov segm_buf,ax ; segm_buf=CS+0x1000
        mov es,ax;es=segm_buf
        add ax, 1000h
        mov seg2, ax    ; seg2=AX+0x1000
        rdtsc
        mov temp,eax; RANDOMIZE()
;загрузить PCX-файл---------------------------------------------------------
        mov dx,offset filename; DS:DX -> ASCIZ filename
        mov ax,3D00h; OPEN DISK FILE for read WITH HANDLE
        int 21h
        mov bx,ax;BX = file handle
        mov cx, 80h;CX = number of bytes to read
        lea dx,hpcx; DS:DX -> buffer
        mov ah, 3Fh; READ FROM FILE WITH HANDLE  
        int 21h
        mov ax, hpcx.PCX_ymax
        sub ax, hpcx.PCX_ymin
        inc ax
        mov ycount,ax
        mov ax,hpcx.PCX_xmax
        sub ax,hpcx.PCX_xmin
        inc ax
        mov local_bytesperline, ax
        xor di, di
        mov si, PCXBUFSIZE
@1:     mov ax,local_bytesperline
        mov xcount,ax
@2:     cmp si,PCXBUFSIZE
        jnz @f
        mov cx, PCXBUFSIZE  ;CX = number of bytes to read 
        lea dx, readbuf ;DS:DX -> buffer
        mov ah, 3Fh; READ FROM FILE WITH HANDLE BX = file handle, 
        int 21h
        xor si, si
@@:     mov al, readbuf[si]
        inc si
        cmp al, 192
        jb  short @3
        and al, 3Fh
        xor cx, cx
        mov cl, al
        sub xcount, cx
        cmp si, PCXBUFSIZE
        jnz @f
        push    cx
        mov cx, PCXBUFSIZE
        lea dx, readbuf
        mov ah, 3Fh; READ FROM FILE WITH HANDLE BX = file handle, 
        int 21h; CX = number of bytes to read DS:DX -> buffer
        pop cx
        xor si, si
@@:     mov al,readbuf[si]
        inc si
        rep stosb
        jmp @f
@3:     dec xcount
        stosb
@@:     cmp xcount,0
        ja  short @2
        dec ycount
        jnz short @1
        or  cx,-1
        mov dx,-(PALSIZE+1)
        mov ax, 4202h; MOVE FILE READ/WRITE POINTER (LSEEK)
        int 21h     ; AL = method: offset from end of file
        mov cx,PALSIZE+1    ; CX = number of bytes to read 
        lea dx,readbuf  ; DS:DX -> buffer
        mov ah, 3Fh     ; READ FROM FILE WITH HANDLE
        int 21h     ; BX = file handle
        lea di, readbuf+1
        mov cx, PALSIZE
@@:     shr byte ptr [di], 2
        inc di
        loop    @b
        xor ax, ax
        mov cx, PALSIZE
        lea si, readbuf+1
        mov dx, 3C8h
        out dx, al
        inc dx
        rep outsb
        mov ah, 3Eh; CLOSE A FILE WITH HANDLE
        int 21h     ; BX = file handle
;инициализируем шары--------------------------------------
        mov i,0
@0:     imul    di,i,sizeof(BALLINFO)
        add di,offset ballinfox 
        call    RAND
        and ax,1Fh
        add ax,10
        assume di: ptr BALLINFO
        mov [di].R, ax
@6:     mov bx,[di].R
        add bx,bx
        mov ax,WIDTH_SCREEN
        sub ax,bx
        mov [di].X,ax
        sub ax,(WIDTH_SCREEN-HEIGHT_SCREEN)
        mov [di].Y,ax
        call    RAND
        xor dx,dx
        div word ptr [di].X
        add dx,[di].R
        mov [di].X, dx
        call    RAND
        xor dx,dx
        div word ptr [di].Y
        add dx,[di].R
        mov [di].Y,dx
;вычисляем расстояние--------------------------------
        xor cx, cx
B1:     cmp i,cx
        jbe short B9
        imul    si,cx,sizeof(BALLINFO)
        add si, offset ballinfox    
        assume si: ptr BALLINFO
        mov ax,[di].X
        sub ax,[si].X
        jnb short @f
        neg ax
@@:     movzx   eax, ax
        mul eax
        mov z,eax
        mov ax,[di].Y
        sub ax,[si].Y
        jnb short @f
        neg ax
@@:     movzx   eax,ax
        mul eax
        add z,eax
        fild    z
        fsqrt
        fistp   z
        mov ax,[si].R
        add ax,[di].R
        cmp word ptr z,ax
        ja  short @f
        mov ax, cx
        jmp short BC
@@:     inc cx
        jmp short B1
B9:     or  ax,-1
BC:     inc ax  ;cmp ax,0FFFFh
        jnz @6
;выбираем направление-------------------------------------
        call    RAND
        and ax,1
        jnz @f
        dec ax
@@:             mov [di].xD,ax
;выбираем направление-------------------------------------
        call    RAND
        and ax, 1
        jnz @f
        dec ax
@@:             mov [di].yD,ax
        mov [di].Clinch,0
        inc i
                cmp i,NUMBALL
        jl  @0
main_loop:  mov dx,IMPUT_STATUS_0;тормозим вывод на экран до следующего кадра
WaitVerticalSync:in al,dx
        test al,8
        jz WaitVerticalSync
WaitNotVerticalSync:in al,dx
        test al,8
        jnz WaitNotVerticalSync
        push    ds
        mov es,seg2
        mov ds,segm_buf
        xor si,si
        xor di,di
        mov cx,SIZESCREEN/4
        rep movsd
        pop ds
        mov i,cx    ;cx=0
@@:     imul    si,i,sizeof(BALLINFO)
        add si,offset ballinfox 
        push    [si].X
        push    [si].Y
        push    [si].R
        call    Ball
        inc i
        cmp i,NUMBALL
        jl  @b
        push    ds
        push    VGA_SEGMENT
        pop es
        mov ds,seg2
        xor si,si
        xor di,di
        mov cx,SIZESCREEN/4
        rep movsd
        pop ds
        xor bx,bx
loc_10512:  imul    di,bx,sizeof(BALLINFO)
        add di,offset ballinfox
        mov ax,[di].X
        add ax,[di].R
        add ax,[di].xD
        cmp ax,WIDTH_SCREEN
        jbe @f
        mov word ptr [di].xD,-1
        jmp short @8
@@:     mov ax,[di].X
        sub ax,[di].R
        add ax,[di].xD
        jge @8
        mov word ptr [di].xD,1
@8:     mov ax,[di].Y
        add ax,[di].R
        add ax,[di].yD
        cmp ax,HEIGHT_SCREEN
        jbe @f
        mov word ptr [di].yD,-1
        jmp @9
@@:     mov ax,[di].Y
        sub ax,[di].R
        add ax,[di].yD
        jge @9
        mov word ptr [di].yD,1
@9:     xor cx, cx
@4:     cmp bx,cx
        jbe loc_10509
        imul    si,cx,sizeof(BALLINFO)
        add si,offset ballinfox 
        mov ax,[di].X
        add ax,[di].xD
        sub ax,[si].X
        jnb @f
        neg ax
@@:     movzx   eax, ax
        mul eax
        mov z,eax
        mov ax,[di].Y
        add ax,[di].yD
        sub ax,[si].Y
        jnb @f
        neg ax
@@:     movzx   eax,ax
        mul eax
        add z,eax
        fild    z
        fsqrt
        fistp   z
        mov ax,[si].R       
        add ax,[di].R
        cmp word ptr z,ax
        ja  @f
        mov ax,cx
        jmp locret_1050C
@@:     inc cx
        jmp @4
loc_10509:  or  ax,-1
locret_1050C:   inc ax;cmp  ax, 0FFFFh
        jz  loc_105CB
        imul    si,ax,sizeof(BALLINFO)
        add si, offset ballinfox - sizeof(BALLINFO)
        mov bx,[di].Clinch
        cmp bx,NUMBALL 
        ja  loc_105BB
        add bx, bx
        jmp switchtable[bx]
 
case0::     neg [si].xD
case1::     neg [di].xD
case2::     neg [si].yD
case3::     neg [di].yD
loc_105BB:  mov ax, [si].xD
        add [si].X,ax
        mov ax,[si].yD
        add [si].Y,ax
        inc [di].Clinch
        jmp loc_105D0
 
 
loc_105CB:  mov [di].Clinch, 0
loc_105D0:  mov ax,[di].xD
        add [di].X,ax
        mov ax,[di].yD
        add [di].Y,ax
        inc bx
        cmp bx,NUMBALL 
        jb  loc_10512
        assume  si: nothing
                assume  di: nothing
        mov ah, 1
        int 16h
        jz  main_loop   
        mov ax, 3
        int 10h     ; - VIDEO - SET VIDEO MODE
        int 20h
start   endp
Ball        proc 
 
Rad_V_Kv    = word ptr -6
Y       = word ptr -4
Z       = word ptr -2
sr      = word ptr  4
Yc      = word ptr  6
Xc      = word ptr  8
 
        enter   6, 0
        push    ds
        mov ax,[bp+sr]
        imul    ax
        mov [bp+Rad_V_Kv],ax
        mov es,seg2
        mov ds,segm_buf
        mov ax,[bp+sr]
        neg ax
        mov [bp+Y],ax   ;Y = -sr
@5:     mov cx,[bp+sr]
        neg cx
@7:     mov bx,cx
        imul    bx,cx          ;bx = (sr)^2
        mov ax,[bp+Y]
        mul ax
        add bx,ax          ;bx = (sr)^2 + Y^2
        cmp [bp+Rad_V_Kv],bx
        jb  @f
        mov ax, [bp+Rad_V_Kv]
        sub ax, bx
        mov [bp+Z], ax
        mov bx, [bp+Xc]
        add bx, cx
        js  @f
        cmp bx, WIDTH_SCREEN
        jnb @f
        mov ax, [bp+Yc]
        add ax, [bp+Y]
        js  @f
        cmp ax, HEIGHT_SCREEN
        jnb @f
        imul    di,ax,WIDTH_SCREEN
        add di, bx
        fild    [bp+Z]
        fsqrt
        fistp   [bp+Z]
        mov bx, [bp+Z]
        add bx, [bp+sr]
        mov ax, [bp+Y]
        imul    [bp+sr]
        idiv    bx
        add ax, [bp+Yc]
        imul    si,ax,WIDTH_SCREEN
        mov ax, cx
        imul    [bp+sr]
        idiv    bx
        add ax, [bp+Xc]
        add si,ax
        movsb
@@:     inc cx
                cmp [bp+sr], cx
        jg  @7
        inc [bp+Y]
        mov ax, [bp+sr]
        cmp [bp+Y], ax
        jl  @5
        pop ds
        leave
        retn    6
Ball        endp
 
RAND        proc 
        mov ax,word ptr temp+2
        mov cx,word ptr temp
        shld    ax,cx,8
        shl cx,8
        rcr ax, 1
        rcr cx, 1
        add word ptr temp, cx
        adc ax,word ptr temp+2
        add word ptr temp,25321
        adc ax,13849
        mov word ptr temp+2,ax
        retn
RAND        endp
 
switchtable dw case0,case1,case2,case3
filename    db 'forest.pcx',0
segm_buf    dw ?
seg2        dw ?
ballinfox   BALLINFO <?>,<?>,<?>
temp        dd ?
end start
Результат
4
Mikl___
Автор FAQ
11376 / 5919 / 535
Регистрация: 11.11.2010
Сообщений: 10,937
10.09.2013, 12:36  [ТС] #29
Что такое стек и для чего он нужен?
Стек — специально отведенная программе область памяти для хранения временных данных, содержимого регистров или адресов команд, следующих за командами call (при вызове процедур). На вершину стека (т.е. последнее записанное значение) указывает пара регистров ss:sp (или ss:esp для 32-разрядных программ). При записи слова (или двойного слова) в стек (командами push, call), оно записывается в память по адресу ss:[sp-2] (или ss:[sp-4], в зависимости от разрядности записываемого значения), а затем из регистра sp/esp вычитается 2 (или 4), то есть стек "растет" в сторону младших адресов памяти. При извлечении значения из стека (командами pop, ret) происходит обратная операция. Для чего это нужно? Предположим, Вам необходимо сохранить значение регистра (допустим, ax), которое понадобиться через несколько команд. Для этого надо выполнить команду push ax, а в том месте, где это значение понадобится — pop ax (или, скажем, pop dx, если Вам необходимо записать это значение в регистр dx). Но! Важно помнить, что запись в стек производится по принципу LIFO (Last In, First Out — последним зашел, первым вышел). Нельзя делать так: push ax ... call ... pushf, а затем pop ax ... popf ... ret, так как в этом случае в регистр ax будут занесено значение регистра флагов, в регистр флагов — адрес возврата из процедуры, а после выполнения команды ret управление перейдет по адресу, который будет взят из сохраненного регистр ax. Таким образом, данные записываются последовательно, а не каждый регистр по определенному адресу! В стек может быть записано как 16-битное, так и 32-битное значение, но не 8-битное. Соответственно, при записи 16-битных значений будет записано 2 байта, а при записи 32-битных значений - 4 байта. Но есть одно исключение: при записи в стек сегментных регистров (ds, es, cs, ss, fs или gs) 32-битные программы (например, программы, работающие под Windows) будут заносить не 2, а 4 байта(!), несмотря на то, что размер сегментных регистров составляет 16 бит. При этом на процессорах Pentium и выше старшее записываемое слово будет содержать нулевое значение. Что же касается команд типа call и ret, то они работают со значениями, размер которых соответствует размеру сегмента кода (то есть 16 бит для DOS-программ, 32 бита — для Win32). Компиляторы ассемблера предлагают несколько модификаций одних и тех же команд, работающих со стеком. Например, команды pusha/popa предназначены для сохранения и восстановления всех (восьми) регистров общего назначения (16-битных значений ax, cx, dx, bx, sp, bp, si и di либо 32-битных eax, ecx и так далее в зависимости от разрядности сегмента кода), а их модификации pushaw/popaw и pushad/popad — 16- и 32-битных соответственно независимо от разрядности сегмента. Что же касается команд ret и retf, то они не имеют модификаций и всегда вынимают из стека столько байт, сколько соответствует текущему размеру сегмента, то есть соответственно 2 и 4 для 16-битных сегментов кода и 4 и 8 для 32-битных. Похожая команда iret имеет модификации iretw и iretd в TASM и только iretd в MASM, причем MASM интерпретирует команду iret как 16-битную (даже в 32-битных программах), а iretd — как 32-битную. Аналогичная ситуация с командами pushf/popf и pushfw/popfw, pushfd/popfd. Записать в стек можно как значение регистра, так и значение из памяти (например, push es:[bx]) и число (push 4000h)

Что такое процедуры и зачем они нужны?
Процедура — некоторая область кода, которая вызывается специальным образом. Механизм вызова отделяет этот код от остального кода. Прежде чем дать определение процедуре, следует рассмотреть ее свойства. Процедура вызывается инструкцией CALL1. При этом в стеке сохраняется адрес возврата (адрес следующей за ветвлением инструкции) и управление передается на процедуру. При возврате инструкцией RET адрес извлекается из стека и на него выполняется переход, при этом из стека может удалится N байт, если инструкция имела вид RET N

В процедуре могут использоваться локальные переменные. Это некоторая область в стеке, "выше" начала которой адрес дна стека не может быть увеличен без возврата из процедуры и разрушения локальных переменных. Для адресации локальных переменных используется регистр ЕВР. Так как архитектура NT требует сохранения в процедурах регистров EBX, ESI, EDI и EBP, то вначале процедуры в стеке сохраняется регистр EBP, затем в этот регистр загружается текущее значение ESP, в стеке аллоцируется пространство под локальные переменные. Далее в пределах процедуры до возврата из нее, значение в регистре EBP не изменяется. Таким образом, этот регистр адресует предыдущее значение регистра EBP (предыдущей процедуры), ниже в стеке рассположены локальные переменные и значения регистров, которые процедура сохранила в стеке при входе и восстановит при выходе, выше адрес возврата из процедуры, еще выше — аргументы переданные процедуре, если у функции есть аргументы, либо локальные переменные принадлежащие "материнской" процедуры, которая вызывала "нашу" процедуру. Начало прцедуры выглядит следующим образом:
Assembler
1
2
3
4
5
6
7
8
push ebp ;сохраняем предыдущее значение регистра ЕВР
mov ebp,esp ; загружаем ссылку в ЕВР
sub esp,32 ;выделяем в стеке 32 байта под локальные переменные
...
mov esp,ebp ;восстанавливаем стек. На дне лежит предыдущее значение регистра ЕВР, 
;выше - адрес возврата из процедуры
pop ebp ; восстанавливаем значение регистра EBP
ret 16 ; удаляем из стека четыре параметра типа DWORD и возвращаемся из процедуры
Стек после выполнения инструкции sub esp,32
ESP -> ... 
 Localsлокальные переменные
EBP -> rEbp
предыдущее значение регистра ЕВР, сохраненное
инструкцией push ebp в начале процедуры
 lpадрес возврата, сохраненный инструкцией Call
 Arg1первый параметр процедуры
 Arg2
второй параметр процедуры (в stdcall-конвенции
вызова аргументы передаются справа налево)
 ... 
Два слова в стеке всегда будут находится рядом, это адрес возврата из функции и сохраненное значение регистра ЕВР. Эта часть стека называется базовым стековым фреймом — Bp-frame2 и описывается структурой STACK_FRAME
Assembler
1
2
3
4
STACK_FRAME struct
rEbp PVOID ? ; предыдущее значение EBP
rEip  PVOID ? ; адрес возврата из процедуры
STACK_FRAME ends
Эту структуру адресует регистр ЕВР в любой момент в пределах текущей процедуры. Очевидно для иной процедуры, вызываемой из данной, в этой структуре будет сохранена ссылка на текущий фрейм, таким образом формируется цепочка стековых фреймов (SFS). Поэтому изменим имена полей структуры следующим образом
Assembler
1
2
3
4
5
STACK_FRAME struct
Next PVOID ? ; ссылка на фрейм (РSTACK_FRAME) предыдущей процедуры, которая
;вызвала текущую. Это предыдущее значение регистра EBP
lp  PVOID ? ; адрес возврата из текущей процедуры
STACK_FRAME ends
Для определения адреса возврата из предыдущей процедуры необходимо взять ссылку на ее стековый фрейм из стекового фрейма текущей процедуры, то есть
lp = STACK_FRAME.Next.lp[ebp]
Такой проход по цепочке стековых фреймов называется бэктресом (back trace). Он может выполняться по достижению последнего стекового фрейма, при этом ссылка на следующий фрейм инвалидна (указывает за пределы стека3)
Относительно начала цепочки стековых фреймов (текущий фрейм, ссылка на который находится в регистре ЕВР контекста) у каждого фрейма есть свой номер. Этот номер определяет, сколько итераций бэктрейса необходимо выполнить для достижения необходимого стекового фрейма и называется номером стекового фрейма (Stack Frame Number — SFN). Так как в пределах текущей процедуры значение ЕВР не меняется, то можно сказать, что код процедуры исполняется на некотором уровне относительно других процедур, от которого зависит SFN (этот уровень равен ΔSFN двух процедур). Этот уровень называется уровнем вложенности процедур (Nesting Level — NL)4.
Важные свойства процедур:
  • NL не зависит от стека, это значение определяется расположением процедур в коде
  • Одно процедурное ветвление повышает NL на 1
  • Одна инструкция Ret понижает NL на 1
  • SFN зависит от NL, то есть SFN=f(NL). При изменении SFN на некоторое значение, NL изменится на то же (все процедуры Bp-based) или большее значение (ΔSFN <= ΔNL). Из данного свойства вытекает следующее — зная NL (он фиксирован) и текущий Bp-фрейм можно определить стековый фрейм искомой процедуры, выполнив NL итераций бэктрейса и таким образом можно получить локальные переменные искомой процедуры, адрес возврата и аргументы
  • Число итераций бэктрейса, необходимых для достижения фрейма целевой процедуры относительно текущей, равно NL текущей процедуры относительно целевой
  • Число инструкций Ret необходимых для возврата в целевую процедуру из текущей равно NL текущей процедуры относительно целевой5
  • Для всех инструкций, начиная от следующих за формирующими Bp-фрейм и до инструкций удаляющих Bp-фрейм, NL остается неизменным (NL=Const) и значение регистра EBP в контексте не изменяется
  • Адрес возврата из процедуры зависит от NL (так как от NL зависит SFN), то есть — lp=f(NL). Область стека процедуры, включая аргументы процедуры, базовый стековый фрейм, локальные переменные и рабочую область стека называют полным стековым фреймом.
  • Базовый стековый фрейм (EBP) находится в стеке выше, чем рабочая область (ESP)
  • Смещение N-ой локальной переменной относительно базового стекового фрейма постоянно и отрицательно (локальные переменные расположены ниже Bp-фрейма)
  • Смещение N-ого аргумента процедуры относительно базового стекового фрейма постоянно и равно
    sizeof(STACK_FRAME) + 4*N
  • Если процедура изменяет регистры EBX, ESI или EDI, то эти регистры должны сохраняться во фрейме по фиксированному для данной процедуры смещению
Теперь можно дать определение процедуры — участок кода, являющийся ветвью, используемый на определенном NL, более высоком, чем у вызывающего кода и содержащий механизм возврата в вызывающий код называется процедурой6) Соответственно инструкция, завершающая вызов процедуры называется процедурным ветвлением.
Стековая маршрутизация
Стековый фрейм содержит адрес возврата из текущей процедуры, следующий фрейм содержит адрес возврата из вызывающей процедуры и так далее, то можно заменить во фрейме адрес возврата. Тогда после снижения NL (SFN) до нулевого уровня (относительно текущей процедуры), из стека будет извлечен соответствующий процедуре фрейм и выполнен возврат на адрес, находящийся в этом фрейме. Так как он изменен, то возврат произойдет на новый адрес. Эта манипуляция называется процедурным post-редиректом или стековой маршрутизацией.
____________________________________________

1 Процедурное ветвление в текущем кодовом сегменте осуществляется инструкцией Call near. Межсегментное ветвление — Call far (селектор описывает кодовый сегмент, а не шлюз вызова) сохраняют в стеке помимо адреса возврата также и селектор кодового сегмента (CS:EIP), куда будет выполнен возврат инструкцией RETF.

2 Для формирования и удаления BP-фреймов предназначены инструкции enter и leave соответственно

3 База и лимит стека находятся в TIB, поля StackBase и StackLimit, смещения которых соответственно +4 и +8 (в ks386 описываются константы с префиксом Tb или Te). В Kernel mode TIB находится в начале PCR, в User mode — в начале TEB. Учитывая, что TEB и PCR адресуются через один и тот же регистр FS, то мод не имеет значения. FS:[+4] в обоих режимах будет адресовать базу стека.

4 По сути это уровень определяющий сколько раз был вызван участок кода через некоторый механизм вызова. В данном случае это процедурное ветвление. Примером не процедурного ветвления может быть ISR — хэндлер прерывания является процедурой, но вызывается через особые механизмы. Непосредственно через инструкции Int, либо косвенно, когда вектор прерывания вызывается при некотором событии (прерывания и исключения). Другим примером могут служить быстрые вызовы сервисных функций ядра посредством инструкций Sysenter. При этом используется совершенно другой механизм (использование MSR), в отличие от прерываний. Важной особенностью процедуры является наличие механизма возврата. Для процедурного ветвления используется инструкция Ret, для прерываний — Iret, для возврата из быстрых сервисных вызовов — инструкция Sysexit и т.д. Механизм вызова и возврата из процедуры не обязательно должен использовать стек (пример — Sysexit). Для ISR NL будет определяться рекурсивным обращением к IDT, например, возникновение #GP из хэндлера #GP увеличит NL

5 Все процедуры Bp-based, то есть формируют стековые фреймы.
5
Mikl___
Автор FAQ
11376 / 5919 / 535
Регистрация: 11.11.2010
Сообщений: 10,937
12.09.2013, 06:11  [ТС] #30
Кто и зачем до сих пор использует DOS. Какой смысл сейчас писать что-либо под эту ОС?
  • Под DOS программировать во многом проще. Структура работы логична и понятна, исходники открыты. Для новичков – DOS лучший полигон для работы.
  • По привычке. Многие начинали программировать, когда еще не было Windows 98/95/NT/XP и прочего.
  • Маленький размер и быстрая работа откомпилированных программ. Маленький размер исходников.
  • Возможность пощупать код на низком уровне, дабы лучше разобраться в работе программы, а не строить её из «кубиков», как часто это делается в Delphi\Buider, после чего непонятно с чем связана та или иная ошибка
  • Пока еще не все машины имеют на борту Windows\Linux, поэтому не стоит хоронить DOS
3
Mikl___
Автор FAQ
11376 / 5919 / 535
Регистрация: 11.11.2010
Сообщений: 10,937
19.09.2013, 06:17  [ТС] #31
Блокируем клавиатуру в Windows
Для блокировки клавиатуры существует несколько путей
  1. функцией BlockInput
  2. блокировкой немаскированных прерываний командой CLI
  3. посылкой в контроллер клавиатуры команды 0xAD
  4. отправим контроллеру клавиатуры адрес, а данные забываем
  5. через реестр
  1. в user32.dll есть функция BlockInput, ниже программа которая отключает клавиатуру на 5 секунд
    Assembler
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    ; masm windows gui #
    .686
    .model flat
    include windows.inc           
    includelib \masm32\lib\kernel32.lib
    includelib \masm32\lib\user32.lib
    extern _imp__BlockInput@4:dword
    extern _imp__Sleep@4:dword
    .code
    start:  push TRUE
        call _imp__BlockInput@4; отключить клаву
        push 5000
        call _imp__Sleep@4;выждать 5 секунд
        push FALSE
        call _imp__BlockInput@4; включить
        ret
    end start
    BlockInput блокирует мышь и почти всю клавиатуру, но имеет недостаток: все это разблокируется при нажатии Ctrl-Alt-Del, внезапном завершении вызвавшей программы, или при критической системной ошибке.
  2. драйвер, который блокирует немаскированные прерывания (главным образом клавиатуру) на 5 секунд.
    Идем сюда, копируем текст драйвера scp00.sys, выкидываем из него всё ненужное пока не получится
    Assembler
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    ; masm windows native #
    ;написано на основе драйвера режима ядра beeper из «KmdTutor by Four-F»
    .686P
    .model flat
    include ntstatus.inc
    include ntddk.inc
    includelib hal.lib
    extern _imp__KeStallExecutionProcessor@4:dword
    .code
    DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
        cli
        mov ecx,1000; 50 мкСек * 1000 = 5 Сек
    @@: push ecx
        push 50; максимальное количество мкСек для функции KeStallExecutionProcessor
        call _imp__KeStallExecutionProcessor@4
        pop ecx
        loop @b    
        sti
        mov eax, STATUS_DEVICE_CONFIGURATION_ERROR;возвращаем код ошибки, для 
        ret     ;того, чтобы система удалила драйвер из памяти
    DriverEntry endp
    запускаем драйвер одним из трех описанных способов
  3. команда 0хAD блокирует клавиатуру до момента разблокировки командой 0хАЕ
    Assembler
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    ; masm windows native #
    .686P
    .model flat
    include ntstatus.inc
    include ntddk.inc
    includelib hal.lib
    extern _imp__KeStallExecutionProcessor@4:dword
    .code
    DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
        mov al,0ADh
        out 64h,al; блокируем
        mov ecx,1000; 50 мкСек * 1000 = 5 Сек
    @@: push ecx
        push 50; максимальное количество мкСек для функции KeStallExecutionProcessor
        call _imp__KeStallExecutionProcessor@4
        pop ecx
        loop @b    
        mov al,0AEh
        out 64h,al;снимаем блокировку
        mov eax, STATUS_DEVICE_CONFIGURATION_ERROR;возвращаем код ошибки, для 
        ret     ;того, чтобы система удалила драйвер из памяти
    DriverEntry endp
    действуем аналогично второму способу
  4. контроллер клавиатуры использует один и тот же порт, как для задания адреса, так и для чтения и записи данных. После передачи кода команды контроллер клавиатуры не будет обрабатывать нажатие клавиш, пока не будет передан байт данных
    Assembler
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    ; masm windows native #
    .686P
    .model flat
    include ntstatus.inc
    include ntddk.inc
    includelib hal.lib
    extern _imp__KeStallExecutionProcessor@4:dword
    .code
    DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
        mov al,0F3h
        out 60h,al; блокируем
        mov ecx,1000; 50 мкСек * 1000 = 5 Сек
    @@: push ecx
        push 50; максимальное количество мкСек для функции KeStallExecutionProcessor
        call _imp__KeStallExecutionProcessor@4
        pop ecx
        loop @b    
        mov al,0F4h
        out 60h,al;снимаем блокировку
        mov eax, STATUS_DEVICE_CONFIGURATION_ERROR;возвращаем код ошибки, для 
        ret     ;того, чтобы система удалила драйвер из памяти
    DriverEntry endp
    аналогично способу два
3
murderer
3314 / 1461 / 133
Регистрация: 06.10.2010
Сообщений: 3,215
22.09.2013, 09:08 #32
Особенности кодинга под x64
Программирование под x64 не сильно отличается от уже привычного x86, но некоторые отличия всё-же имеются. Как всегда для того, чтобы узнать все эти мелочи приходится собирать инфу в нескольких источниках. Я попытаюсь собрать воедино эти мелочи из руководств Intel, MSDN и руководства FASM.
По части инструкций
Запилено:
  1. 8 xmm-регистров (xmm8-xmm15).
  2. 8 регистров общего назначения (r8-r15)
  3. Теперь для относительной адресации используется регистр rip.
  4. Теперь можно обращаться к младшим байтам индексных регистров (bpl, spl, dil, sil)

Выпилено:
  1. Инструкции для работы с двоично-десятичными числами (aaa, aad, aam, aas, daa и das)
  2. bound
  3. pusha/popa
  4. inc и dec с однобайтовой кодировкой

Ограничения:
  1. В архитектуре x86 присутствует ограничение на длину инструкции в 15 байт. Из этого вытекает невозможность записи инструкций вида
    Assembler
    1
    
    mov [mem64],imm64
    Так как это займёт больше 15 байт (8 байт адреса + 8 байт непосредственного операнда + опкод). Поэтому в x64 можно делать только так
    Assembler
    1
    
    mov [mem64],imm32
    При этом imm32 будет преобразован в qword с учётом знака (movsx).
  2. Инструкция не может ссылаться одновременно на ah,bh,dh,ch и младший байт новых регистров. То есть "mov ah,dl" - допустимо, а "mov ah,r8b" - не допустимо.
  3. Операции с 32 битными операндами обнуляют старшие 4 байта результата. То есть "add eax,ebx" обнулят старшую часть rax. К 8 и 16 битным операндам это не относится.

Соглашение fastcall x64
Передача параметров
Первые 4 параметра передаются через регистры rcx, rdx, r8, r9, остальные параметры передаются в обратном порядке через стек. При этом в стеке резервируется дополнительно 32 байта для первых 4 параметров даже если у функции вообще нет параметров. Параметры с плавающей точкой передаются через регистры xmm0, xmm1, xmm2, xmm3. Если параметр занимает больше 8 байт, он передаётся через указатель. Стек освобождается вызывающей стороной.

Выравнивание стека
Перед вызовом функции rsp должен быть кратен 16. Таким образом если ваша функция имеет чётное количество параметров и количество параметров больше 4, при передаче ей управления rsp будет кратен 8, иначе rsp кратен 16. В FASM макросы .code/.end автоматически добавляют "sub rsp,8" в точке входа для выравнивания стека.

Не изменяемые регистры
Функции не должны изменять содержимое регистров rsi, rdi, rbp, rbx, r12-r15, xmm6-xmm15.

Общие рекомендации
  1. Старайтесь обходиться 32-битными инструкциями, они занимают меньше места, чем 64-битные т.к. для кодирования 64-битных инструкций используется префикс REX. Не стоит забывать, что регистры rbl, spl, dil, sil также кодируются через префикс.
  2. В FASM существует макрос frame/endf, который позволяет выделять и освобождать место в стеке для вызова сразу нескольких API-функций.
    Рассмотрим такой ничего не значащий код
    Assembler
    1
    2
    
    invoke MessageBoxW,rcx,rdx,r8,r9
    invoke ExitProcess,rcx
    Он будет скомпилирован так
    Assembler
    1
    2
    3
    4
    5
    6
    
    sub  rsp,32
    call [MessageBoxW]
    add  rsp,32
    sub  rsp,32
    call [ExitProcess]
    add  rsp,32
    Теперь применим макрос
    Assembler
    1
    2
    3
    4
    
    frame
    invoke MessageBoxW,rcx,rdx,r8,r9
    invoke ExitProcess,rcx
    endf
    И всё заметно улучшится
    Assembler
    1
    2
    3
    4
    
    sub  rsp,32
    call [MessageBoxW]
    call [ExitProcess]
    add  rsp,32
    Кстати обратите внимание: умный макрос invoke не генерирует лишний код
    Assembler
    1
    2
    3
    4
    
    mov rcx,rcx
    mov rdx,rdx
    mov r8,r8
    mov r9,r9
    Можно учитывать это при написании программ.
  3. До появления ollydbg x64 можно использовать PEBrowseDbg64 - вполне годный отладчик. При запуске отладки происходит остановка в ntdll.dll, нажимаем F5 и попадаем на точку входа нашего PE. Присутствует небольшой баг: если начать отладку и просто закрыть отладчик, то он останется висеть в памяти. Для корректного выхода нужно нажать Shift+F5 (завершить отладку), а уже после этого закрывать отладчик.
8
Полный 30h
Эксперт быдлокодинга
1532 / 445 / 61
Регистрация: 04.11.2010
Сообщений: 1,219
22.09.2013, 23:25 #33
murderer,
Assembler
1
2
Start:
  sub rsp, 8
0
Mikl___
Автор FAQ
11376 / 5919 / 535
Регистрация: 11.11.2010
Сообщений: 10,937
23.09.2013, 05:04  [ТС] #34
Особенности кодинга под x64
(часть вторая)
За основу взята статья Крис Касперски "Архитектура x86-64 под скальпелем ассемблерщика"

За подробным описанием x86-64 архитектуры лучше всего обратится к фирменной документации "AMD64 Technology - AMD64 Architecture Programmer's Manual Volume 1:Application Programming" (http://www.amd.com/us-en/assets/cont...docs/24593.pdf). Мы ограничимся только беглым обзором основных нововведений.
К восьми регистрам общего назначения добавилось еще восемь, в результате чего их общее количество достигло 16.
Старые регистры, расширенные до 64-бит, получили имена RAX, RBX, RCX, RDX, RBP, RSI, RDI, RSP, RIP и RFLAGS. Новые регистры остались безымянными и просто пронумерованы от R8 до R15. Для обращения к младшим 8-, 16- и 32-битам новых регистров можно использовать суффиксы b, w и d соответственно. Например, R9 - это 64-разрядный регистр, R9b - его младший байт (по аналогии с AL), а R9w - младшее слово (то же самое, что AX в EAX). Прямых наследников AH, к сожалению, не наблюдается и для манипуляции со средней частью регистров приходится извращаться со сдвигами и математическими операциями.

Регистры, доступные в x86-64 режиме

Регистр-указатель команд RIP теперь адресуется точно так же, как и все остальные регистры общего назначения.
Простейший пример: загрузить в регистр AL опкод следующей машинной команды. На x86 приходится поступать так. Если мы знаем адрес следующей команды, тогда это выглядит так
Assembler
1
2
        mov al,ds:[40430h]
        nop
если адрес неизвестен
Assembler
1
2
3
4
        call $ + 5; в стек адрес следующей команды и передать на нее управление
        pop ebx; вытолкнуть из стека адрес возврата, скорректировать адрес на размер 
        mov al, [ebx+7]  ;команд pop/mov, теперь AL содержит опкод команды NOP
        NOP            ; команда, опкод которой мы хотим загрузить в AL
Загрузка опкода следующей машинной команды в классическом x86

А теперь перепишем тот же самый пример на x86-64:
Assembler
1
2
       mov al,[rip]   ; загружаем опкод следующей машинной команды
        NOP            ; команда, чем опкод мы хотим загрузить в AL
Загрузка опкода следующей машинной команды на x86-64.

Только следует помнить, что RIP всегда указывает на следующую, а не на текущую инструкцию. К сожалению, ни Jx RIP, ни CALL RIP не работают. Таких команд в x86-64 просто нет. Исчезла абсолютная адресация. Если нам надо изменить содержимое ячейки памяти по конкретному адресу, на x86 мы поступаем приблизительно так:
Assembler
1
       dec byte ptr ds:[666h]  ; уменьшить содержимое байта по адресу 666h на единицу
Абсолютная адресация в классическом x86

Под x86-64 транслятор выдает ошибку ассемблирования, вынуждая нас прибегать к фиктивному базированию:
Assembler
1
2
        xor r9, r9              ; обнулить регистр r9
        dec byte ptr [r9+666h]  ; уменьшить содержимое байта по адресу 666h на единицу
Использование фиктивного базирования на x86-64 для абсолютной адресации

Есть и другие отличия от x86, но они не столь принципиальны. Важно то, что в режиме совместимости с x86 (Legacy Mode) ни 64-битные регистры, ни новые методы адресации недоступны! Никакими средствами дотянуться до них нельзя и прежде чем что-то сделать, необходимо перевести процессор в режим "long mode", который делится на два под-режима:
  • режим совместимости с x86 "compatibility mode"
  • 64-битный режим (64-bit mode)
Режим совместимости предусмотрен только для того, чтобы 64-разрядная операционная система могла выполнять старые 32-битные приложения. Никакие 64-битные регистры здесь и не ночевали, поэтому нам этот режим фиолетов.
Реальная 64-битность обитает только в 64-bit long mode, о котором мы и будем говорить!

Режимы работы процессора AMD-64 и их особенности

"Hello world" на x86-64

Программирование под 64-битную версию Windows мало чем отличается от традиционного, только все операнды и адреса по умолчанию 64-разрядные, а параметры API-функций передаются через регистры, а не через стек. Первые четыре аргумента всех API-функций передаются в регистрах RCX, RDX, R8 и R9 (регистры перечислены в порядке следования аргументов, крайний левый аргумент помещается в RCX). Остальные параметры кладутся на стек. Все это называется x86-64 fast calling conversion (соглашение о быстрой передаче параметров для x86-64), подробное описание которой можно найти в статье "The history of calling conventions, part 5 amd64" (http://blogs.msdn.com/oldnewthing/archive/2004/01/14/58579.aspx). Также нелишне заглянуть на страничку бесплатного компилятора Free PASCAL и поднять документацию по способам вызова API: http://www.freepascal.org/wiki/index.php/Win64/AMD64_API.
В частности, вызов функции с пятью аргументами API_func(1,2,3,4,5) выглядит так:
Assembler
1
2
3
4
5
6
        mov     dword ptr [rsp+20h], 5 ; кладем на стек пятый слева аргумент
        mov     r9d, 4                 ; передаем четвертый слева аргумент
        mov     r8d, 3                 ; передаем третий слева аргумент
        mov     edx, 2                 ; передаем второй слева аргумент
        mov     ecx, 1                 ; передаем первый слева аргумент
        call    API_func
Пример вызова API-функции с пятью параметрами по соглашению x86-64.

Смещение пятого аргумента относительно верхушки стека требует пояснений. Почему оно равно 20h? Ведь адрес возврата занимает только 8 байт. Кто съел все остальные? Оказывается, они "резервируются" для первых четырех аргументов, переданных через регистры. "Зарезервированные" ячейки содержат неинициализированный мусор и называются "spill", что переводится как "затычка" или "потеря".
Вот минимум знаний, необходимых для выживания в мире 64-битной Windows при программировании на ассемблере. Остается разобрать самую малость. Как эти самые 64-бита заполучить?! Для перевода FASM'а в x86-64 режим достаточно указать директиву "use64" и программировать, как обычно.
Ниже идет пример простейшей x86-64 программы, которая не делает ничего, только возвращает в регистре RAX значение "ноль".
Assembler
1
2
3
4
5
6
; сообщаем FASM'у, что мы хотим программировать на x86-64
        use64
 
        xor r9,r9        ; обнуляем регистр r9
        mov rax,r9       ; пересылаем в rax,r9 (можно сразу mov rax,0, но неинтересно)
        ret              ; выходим туда, откуда пришли
Простейшая 64-битная программа.

Никаких дополнительных аргументов командной строки указывать не надо, просто сказать
Код
fasm file-name.asm
Через несколько секунд образуется файл file-name.bin, который в hex-представлении выглядит следующим образом:
Код
4D 31 C9        xor        r9, r9
4C 89 C8        mov        rax, r9
C3              retn
Дизассемблерный листинг простейшей 64-битной программы.

Формально, это типичный com-файл
Начиная с версии 1.64, ассемблер FASM поддерживает специальную директиву "format PE64", автоматически формирующую 64-разрядный PE-файл (директиву "use64" в этом случае указывать уже не нужно), а в каталоге EXAMPLES можно найти готовый пример PE64DEMO, в котором показано, как ее использовать на практике.
Ниже приведен пример x86-64 программы "hello, world" с комментариями:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
; пример 64-битного PE файла
; для его выполнения необходимо иметь Windows XP 64-bit edition
; указываем формат
format PE64 GUI
; указываем точку входа
entry start
; создать кодовую секцию с атрибутами на чтение и исполнение
section '.code' code readable executable
start:  mov        r9d,0                   ; uType == MB_OK (кнопка по умолчанию)
                                           ; аргументы по соглашению x86-64
                                           ; передаются через регистры, не через стек!
                                           ; префикс d задает регистр размером в слово,
                                           ; можно использовать и mov r9,0, но тогда
                                           ; машинный код будет на байт длиннее
        lea        r8,[_caption]           ; lpCaption, передаем смещение
                                           ; команда lea занимает всего 7 байт,
                                           ; а mov reg,offset - целых 11, так что
                                           ; lea намного более предпочтительна
        lea        rdx,[_message]          ; lpText, передаем смещение выводимой строки
        mov        rcx,0                   ; hWnd, передам дескриптор окна-владельца
                                           ; (можно также использовать xor rcx,rcx
                                           ; что на три байта короче)
        call       [MessageBox]            ; вызываем функцию MessageBox
        mov        ecx,eax                 ; заносим в ecx результат возврата
                                           ; (Функция ExitProcess ожидает 32-битный параметр;
                                           ; можно использовать и mov rcx,rax, но это будет
                                           ; на байт длиннее)
        call       [ExitProcess]           ; вызываем функцию ExitProcess
; создать секцию данных с атрибутами на чтение и запись
; (вообще-то, в данном случае атрибут на запись необязателен,
; поскольку мы ничего не пишем, а только читаем)
section '.data' data readable writeable
  _caption db 'PENUMBRA is awesome!',0     ; ASCIIZ-строка заголовка окна
  _message db 'Hello World!',0             ; ASCIIZ-строка выводимая на экран
; создать секцию импорта с атрибутами на чтение и запись
; (здесь атрибут на запись обязателен, поскольку при загрузке PE-Файла
; в секцию импорта будут записываться фактические адреса API-функций)
section '.idata' import data readable writeable
        dd 0,0,0,RVA kernel_name,RVA kernel_table
        dd 0,0,0,RVA user_name,RVA user_table
        dd 0,0,0,0,0     ; завершаем список двумя 64-разряными нулеми!!!
kernel_table:
        ExitProcess dq RVA _ExitProcess
        dq 0                        ; завершаем список 64-разряным нулем!!!
user_table:
        MessageBox dq RVA _MessageBoxA
        dq 0
kernel_name db 'KERNEL32.DLL',0
user_name db 'USER32.DLL',0
_ExitProcess dw 0
        db 'ExitProcess',0
_MessageBoxA dw 0
        db 'MessageBoxA',0
64-битное приложение "hello, world" под Windows на FASM'е
Код
.code:0000000000401000 41 B9 00 00 00 00      mov        r9d, 0
.code:0000000000401006 4C 8D 05 F3 0F 00 00   lea        r8, aPENUMBRA
.code:000000000040100D 48 8D 15 03 10 00 00   lea        rdx, aHelloWorld ; "Hello World!"
.code:0000000000401014 48 C7 C1 00 00 00 00   mov        rcx, 0
.code:000000000040101B FF 15 2B 20 00 00      call       cs:MessageBoxA
.code:0000000000401021 89 C1                  mov        ecx, eax
.code:0000000000401023 FF 15 13 20 00 00      call       cs:ExitProcess
Дизассемблерный листинг 64-битного приложения "Hello, world!"

приложение в стиле MASM. Никаких радикальных отличий не наблюдается:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
; объявляем внешние API-функции, которые мы будем вызывать
extrn MessageBoxA: PROC
extrn ExitProcess: PROC
; секция данных с атрибутами по умолчанию (чтение и запись)
.data
mytit db 'PENUMBRA is awesome!', 0
mymsg db 'Hello World!', 0
; секция кода с атрибутами по умолчанию (чтение и исполнение)
.code
Main:   mov r9d, 0          ; uType = MB_OK
        lea r8,  mytit      ; LPCSTR lpCaption
        lea rdx, mymsg      ; LPCSTR lpText
        mov rcx, 0          ; hWnd = HWND_DESKTOP
        call MessageBoxA
        mov ecx, eax        ; uExitCode = MessageBox(...)
        call ExitProcess
End Main
64-битное приложение "Hello, world" под Windows на MASM'е

Ассемблирование и линковка проходит так:
Код
ml64 XXX.asm /link /subsystem:windows /defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main
в результате чего образуется готовый к употреблению exe-файл
9
Mikl___
Автор FAQ
11376 / 5919 / 535
Регистрация: 11.11.2010
Сообщений: 10,937
23.09.2013, 18:05  [ТС] #35
Особенности кодинга под x64
(часть третья)
Простое оконное приложение на Win64
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
format PE64 GUI 5.0
entry start
 
include 'win64a.inc'
 
section '.data' data readable writeable
 
main_hwnd   dq ?
msg     MSG
wc      WNDCLASS
    
hInst       dq ?
szTitleName db 'Window work sample Win64',0
szClassName db 'ASMCLASS32',0
 
button_class    db 'BUTTON',0
AboutTitle  db 'About',0
AboutText   db 'First win64 window program',0;
ExitTitle   db 'Exit',0
 
AboutBtnHandle  dq ?
ExitBtnHandle   dq ?
 
section '.code' code executable readable
 
start:  sub rsp, 8*5           ; align stack and alloc space for 4  parameters
 
    xor rcx, rcx
    call [GetModuleHandle]
 
    mov [hInst], rax
    mov [wc.style], CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS
    mov rbx, WndProc
    mov [wc.lpfnWndProc],  rbx
    mov [wc.cbClsExtra], 0
    mov [wc.cbWndExtra], 0
    mov [wc.hInstance], rax
 
    mov rdx, IDI_APPLICATION
    xor rcx, rcx
    call [LoadIcon]
    mov [wc.hIcon], rax
 
    mov rdx, IDC_ARROW
    xor rcx, rcx
    call [LoadCursor]
    mov [wc.hCursor], rax
 
    mov [wc.hbrBackground], COLOR_BACKGROUND+1
    mov qword [wc.lpszMenuName], 0
    mov rbx, szClassName
    mov qword [wc.lpszClassName], rbx
 
    mov rcx, wc
    call [RegisterClass]
 
    sub rsp, 8*8    ; alloc place in stack for 8 parameters
 
    xor rcx, rcx
    mov rdx, szClassName
    mov r8, szTitleName
    mov r9, WS_OVERLAPPEDWINDOW
    mov qword [rsp+8*4], 50
    mov qword [rsp+8*5], 50
    mov qword [rsp+8*6], 300
    mov qword [rsp+8*7], 250
    mov qword [rsp+8*8], rcx
    mov qword [rsp+8*9], rcx
    mov rbx, [hInst]
    mov [rsp+8*10], rbx
    mov [rsp+8*11], rcx
    call [CreateWindowEx]
    mov [main_hwnd], rax
 
    xor rcx, rcx
    mov rdx, button_class
    mov r8, AboutTitle
    mov r9, WS_CHILD
    mov qword [rsp+8*4], 50
    mov qword [rsp+8*5], 50
    mov qword [rsp+8*6], 200
    mov qword [rsp+8*7], 50
    mov rbx, [main_hwnd]
    mov qword [rsp+8*8], rbx
    mov qword [rsp+8*9], rcx
    mov rbx, [hInst]
    mov [rsp+8*10], rbx
    mov [rsp+8*11], rcx
    call [CreateWindowEx]
    mov [AboutBtnHandle], rax
 
    xor rcx, rcx
    mov rdx, button_class
    mov r8, ExitTitle
    mov r9, WS_CHILD
    mov qword [rsp+8*4], 50
    mov qword [rsp+8*5], 150
    mov qword [rsp+8*6], 200
    mov qword [rsp+8*7], 50
    mov rbx, [main_hwnd]
    mov qword [rsp+8*8], rbx
    mov qword [rsp+8*9], rcx
    mov rbx, [hInst]
    mov [rsp+8*10], rbx
    mov [rsp+8*11], rcx
    call [CreateWindowEx]
 
    mov [ExitBtnHandle], rax
 
    add rsp, 8*8         ; free place in stack
 
    mov rdx, SW_SHOWNORMAL
    mov rcx, [main_hwnd]
    call [ShowWindow]
 
    mov rcx, [main_hwnd]
    call [UpdateWindow]
 
    mov rdx, SW_SHOWNORMAL
    mov rcx, [AboutBtnHandle]
    call [ShowWindow]
 
    mov rdx, SW_SHOWNORMAL
    mov rcx, [ExitBtnHandle]
    call [ShowWindow]
 
  msg_loop: xor r9, r9
    xor r8, r8
    xor rdx, rdx
    mov rcx, msg
    call  [GetMessage]
 
    cmp rax,1
    jb  end_loop
    jne msg_loop
 
    mov rcx,  msg
    call [TranslateMessage]
    mov rcx,  msg
    call [DispatchMessage]
    jmp msg_loop
 
  end_loop:
    xor rcx, rcx
    call [ExitProcess]
Код не содержит ничего нового, кроме вызова fastcall-функций. В самом начале
Assembler
1
sub rsp,8*5
резервируется место в стеке для четырех параметров и происходит выравнивание стека на границу 16 байт (32 байта для параметров + 8 байт для выравнивания = 40 байт). Перед использованием функции CreateWindowsEx в стеке резервируется дополнительно 64 байта под восемь параметров для этой функции (функция принимает двенадцать параметров; место под четыре параметра уже зарезервировано).
Основной код программы написан — теперь осталось добавить функцию окна
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
proc WndProc hwnd, wmsg, wparam, lparam
    ;
    ; rdx = wmsg
    ; r8  = wparam
    ; r9  = lparam
    ; stack aligned!
    sub rsp, 8*4  ;  alloc space for 4  parameters
 
    cmp rdx, WM_DESTROY
    je  .wmdestroy
    cmp rdx, WM_COMMAND
    jne .default
    mov rax, r8
    shr rax, 16
    cmp rax, BN_CLICKED
    jne .default
    cmp r9, [AboutBtnHandle]
    je  .about
    cmp r9, [ExitBtnHandle]
    je  .wmdestroy
 
.default:   call [DefWindowProc]
    jmp .finish
 
.about: xor rcx, rcx
    mov rdx, AboutText
    mov r8, AboutTitle
    xor r9, r9
    call [MessageBox]
    jmp .finish
 
.wmdestroy: xor rcx, rcx
    call [ExitProcess]
.finish:
    add rsp, 8*4  ; restore stack
    ret
endp
В оконной функции для вызова функции DefWindowProc и MessageBox сразу резервируется в стеке место для четырех параметров. В начале оконной функции происходит проверка кода сообщения (код содержится в регистре RDX), и, если этот код не равен ни WM_DESTROY, ни WM_COMMAND, то происходит вызов функции DefWindowProc. При этом нет необходимости передавать ей параметры — все параметры уже находятся на своих местах, так как и функция окна и функция DefWindowProc используют одни и те же параметры.
В оконной функции нет необходимости выравнивать указатель стека на 16 байт, так как системная функция, которая вызывает оконную функцию, использует выровненный стек.
Завершаем программу описанием импортируемых функций
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
section '.relocs' fixups readable writeable
 
section '.idata' import data readable writeable
 
  library kernel,'KERNEL32.DLL',\
      user,'USER32.DLL'
 
  import kernel,\
     GetModuleHandle,'GetModuleHandleA',\
     ExitProcess,'ExitProcess'
 
  import user,\
     RegisterClass,'RegisterClassA',\
     CreateWindowEx,'CreateWindowExA',\
     DefWindowProc,'DefWindowProcA',\
     GetMessage,'GetMessageA',\
     TranslateMessage,'TranslateMessage',\
     DispatchMessage,'DispatchMessageA',\
     LoadCursor,'LoadCursorA',\
     LoadIcon,'LoadIconA',\
     ShowWindow,'ShowWindow',\
     UpdateWindow,'UpdateWindow',\
     MessageBox,'MessageBoxA'
4
Mikl___
Автор FAQ
11376 / 5919 / 535
Регистрация: 11.11.2010
Сообщений: 10,937
29.09.2013, 09:31  [ТС] #36
Где скачать компилятор для ассемблера?
Компиляторы языков высокого уровня (Си, Паскаль) в определенной степени совместимы между собой и хотя исходный текст, предназначенный для одного компилятора, не всегда без переделок транслируется на другом, синтаксис и прочие языковые концепции остаются неизменными, позволяя "летать" между MS VC, Intel C++, GCC, Open WATCOM, сравнивая полноту поддержки Стандарта, скорость трансляции, качество кодогенерации, популярность компилятора и вытекающее отсюда изобилие (недостаток) библиотек и компонентов к нему.
С трансляторами ассемблера все обстоит иначе. Казалось бы, x86 ассемблер — он и в Африке ассемблер, но нет! Кроме поддержки мнемоник машинных команд, каждый транслятор обладает своим собственным набором директив и макросредств, зачастую ни с чем не совместимых. Ассемблерный листинг под MASM, бесполезно переносить на FASM, поскольку возможности, предоставляемые макросредствами, у них неодинаковые.
Человеку, упорно игнорирующего существование Linux/BSD, абсолютно все равно, на какое количество платформ был перенесен данный транслятор. А для кого-то это вопрос первостепенной важности!
Тем не менее, существует ряд основополагающих критериев, существенных для всех категорий программистов. Начнем с генерации отладочной информации, без которой отладка программы сложнее, чем "hello, word", превращается в настоящую пытку. Кое-кто попытается возразить, что ассемблерам, в отличии от языков высокого уровня, отладочная информация не нужна, ведь мнемоники машинных команд, что в листинге, что в отладчике — одни и те же. А метки?! А структуры?! А имена функций?! Уберите их — и код станет совершенно не читаемым! Можно воспользоваться отладочной печатью (просто вставлять макрос, выводящий значение регистров/переменных на экран/файл в указанных местах). Еще можно отлаживать программу, держа перед носом распечатку исходных текстов.
Проблема в том, что формат отладочной информации не стандартизован и различные трансляторы используют различные форматы, что ограничивает в выборе отладчиков или вынуждает использовать конвертеры сторонних производителей, FASM вообще не генерирует отладочной информации.
Если формат отладочной информации — это, скорее, проблема для профессионалов, то формат выходных файлов касается всех. Обыкновенный obj, из которого с помощью линкера можно изготовить все, что угодно — от exe до dll. Объектные файлы делятся на
  • omf (в редакциях от Microsoft и IBM)
  • coff, elf, aout
  • кучу разной экзотики в стиле as86, rdf, ieee и т.д
Также заслуживает внимания возможность "сквозной" генерации двоичных файлов, не требующая помощи со стороны линкера. А некоторые ассемблеры (например, FASM) даже позволяют "вручную" генерировать исполняемые файлы и динамические библиотеки различных форматов, полностью контролируя процесс их создания и заполняя ключевые поля по своему усмотрению. Впрочем, программы, целиком написанные на ассемблере — это либо вирусы, либо демки, либо учебные. Обычно на ассемблере пишутся лишь системно-зависимые компоненты или модули, критичные к быстродействию, которые затем линкуются к основному проекту и, если ассемблер генерирует только omf, а компилятор — coff, тогда возникает проблема сборки "разнокалиберных" форматов воедино. Линкер, умеющий это делать — ulink от Юрия Харона, он же обеспечивает возможности по сборке файлов "вручную", так что выбор конкретного ассемблерного транслятора целиком лежит на совести (и компетенции) программиста, но все-таки лучше, чтобы и ассемблер, и компилятор генерировали одинаковые форматы объектных файлов.
Другой немаловажный критерий — количество поддерживаемых процессорных архитектур, которых в линейке x86 набралось уже больше десятка. Конечно, недостающие команды можно реализовать с помощью макросов или запрограммировать непосредственно в машинном коде через директиву DB, но... если так рассуждать, то зачем вообще нужны ассемблеры, когда есть hex-редакторы?! Особое внимание следует обратить на платформы AMD x86-64 и Intel IA64. 64-разрядные архитектуры вытесняют x86, поэтому учиться программировать под них обязательно, так что поддержка со стороны транслятора должна быть обеспечена уже сейчас!
Ни один из трансляторов не поддерживает набор команд x86-процессоров в полном объеме. Например, на MASM'е невозможно написать jmp 0007h:00000000h и приходится прибегать к различным ухищрениям: либо реализовать команду через DB, что ненаглядно и неудобно, либо заталкивать в стек сегмент/смещение, а потом делать retf, но это длинно и к тому же воздействует на стек, которого у нас может и не быть.
Программирование на смеси 16- и 32-разрядного кода с кратковременным переходом в защищенный режим и возвращением обратно в реальный — на MASM'е скорее умрешь, чем такое запрограммируешь, но большинству программистов подобное трюкачество просто не нужно!
А вот что реально нужно большинству — так это интеграция в мировое сообщество. Свои собственные оси обычно пишут новички, только что начитавшиеся Юрова/Зубкова и открывшие для себя защищенный режим с его безграничными возможностями. В учебном плане это очень даже хорошо, но коммерческим программистам обычно приходится программировать под уже существующие системы — ту же Windows, например. И, если в состав DDK входит MASM в кучей исходных текстов драйверов, то пытаться собрать их под другим транслятором означает впустую убивать время. Опять-таки, если компилятору Microsoft Visual C++ задать ключ /FA, то он выдаст ассемблерный листинг в стиле MASM, точно так же поступит и IDA Pro, Borland C++ выберет TASM, а GCC - GNU Assembler (он же GAS). Вот это и называется интеграцией в среду программирования. Чистый ассемблер сам по себе мало кому интересен и практически всегда становится приложением к чему-то еще. Если вы пишите драйвера под Windows на Microsoft Visual C++, то разумнее всего остановить свой выбор на MASM'е, поклонникам же Borland C++ лучше TASM'а ничего не найти. Под Linux/BSD рулит GAS (GNU Assembler) уже хотя бы за счет того, что ассемблерные программы можно транслировать с помощью компилятора GCC, используя Си-сопроцессор с одной стороны и освобождаясь от головной боли с поиском стартового кода и библиотек, однако GAS использует AT&T-синтаксис, являющийся полной противоположностью Intel-синтаксису, которого придерживаются MASM, TASM, FASM, NASM/YASM. Разработчики вирусов и просто маленьких ассемблерных программ, написанных из любви к искусству, намного меньше ограничены в своем выборе и могут использовать все, что им по душе, вне зависимости от степени "поддержки".
Качество документирования играет весьма важную роль и еще важнее — кому принадлежит проект. Трансляторы, созданные энтузиастами-одиночками, могут умереть в любой момент, поэтому закладываться на них в долговременной перспективе не стоит. Коммерческие ассемблеры крупных компаний выглядят намного более стабильными и непоколебимыми, однако никаких гарантий, что в один "прекрасный" момент компания не забросит на своей продукт, у нас нет (достаточно вспомнить историю с TASM'ом). Открытые трансляторы, поддерживаемые независимой группой лиц, наиболее живучи. Стоило коллективу NASM'а чуть-чуть приостановить его развитие, как тут же появился YASM — "позаимствовавший" исходные тексты и добавивший все необходимое (поддержку x86-64, формат отладочной информации CodeView и так далее).
Последнее, на чем хотелось бы заострить внимание — это макросредства. Отношение к ним у программистов двоякое. Одни во всю пользуются плодами прогресса, программируя на смеси ассемблера, бейсика и препроцессора Си (существует даже проект HLA: High Level Assembler — Ассемблер Высокого Уровня), другие — презирают их, ратуя за чистоту ассемблерного кода. Макросы упрощают программирование, зачастую позволяя невозможное (например, шифровать код программы еще на стадии ассемблирования!), но переносимость программы от этого резко ухудшается и переход на другой транслятор становится труднореализуемым. Но как бы там ни было, поддержка макросов совсем не обязывает этими макросами пользоваться!
15
Мотороллер
431 / 242 / 43
Регистрация: 05.08.2013
Сообщений: 1,661
Завершенные тесты: 1
06.10.2013, 18:27 #37
сортировка вставками.
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
format PE console
include 'include\win32a.inc'
entry start
 
section '.data' data readable writeable
        array dd 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -100500
        size=($-array)/4
 
section '.code' code readable executable
start:
        stdcall insert_sort, array, size
        invoke ExitProcess, 0
 
insert_sort:
i equ ecx
j equ edx
        push ebp
        mov ebp, esp
        pushad
 
        mov esi, [ebp+8]     ;esi=array
        mov i, 1             ;i=1
is1:    cmp i, [ebp+12]      ;i<size
        ja is_exit
        mov eax, [esi+i*4]    ;eax=key=array[i]
        mov j, i              ;j=i-1
        dec j
is2:    cmp j, 0              ;j>=o
        jl is3
        cmp [esi+j*4], eax    ;array[j]>key
        jl is3
        mov ebx, [esi+j*4]    ;array[j+1]=array[j]
        mov [esi+j*4+4], ebx
        dec j                 ;j-=1
        jmp is2
is3:
        mov [esi+j*4+4], eax  ;array[j+1]=key
        inc i
        jmp is1
is_exit:
        popad
        leave
        ret 8
 
section '.idata' import data readable writeable
library KERNEL32, 'KERNEL32.DLL'
 
import KERNEL32,\
       ExitProcess, 'ExitProcess'
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
 
void print_array(int*, int);
void ins_sort(int*, int);
 
int main(int argc, char *argv[])
{
  int array[]={9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -500};
  print_array(array, sizeof(array)/sizeof(int));
  putchar('\n');
  ins_sort(array, sizeof(array)/sizeof(int));
  print_array(array, sizeof(array)/sizeof(int));
  getchar();
  return 0;
}
 
 
void print_array(int* array, int size)
{
     int i;
     for (i=0; i<size; ++i)
         printf("%d ", array[i]);
     
}
 
void ins_sort(int* array, int size)
{
     int i, j, key;
     for (i=1; i<size; ++i)
     {
         key=array[i];
         j=i-1;
         while (j>=0 && array[j]>key)
         {
               array[j+1]=array[j];
               j-=1;
         }
         array[j+1]=key;
     }
}
4
Not at all!
193 / 190 / 18
Регистрация: 06.10.2013
Сообщений: 360
13.11.2013, 12:24 #38
Как известно, ml64.exe не имеет встроенного макроса invoke, поэтому код сильно разбухает из-за необходимости передавать аргументы функций вручную. Представляю макрос, который облегчит труд программиста.

Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
invoke macro function,args:VARARG
local txt,arg,arg1
txt textequ <>
cnt = 0
 
for arg,<args>
txt catstr <arg>,<!,>,txt
cnt = cnt+1
endm
 
n = cnt
k=n mod 2
 
IF n gt 4
sub rsp,(n+k)*8
ELSE
sub rsp,20h
ENDIF
 
% for arg,<txt>
cnt = cnt-1
m=0
 
len sizestr <arg>
addr_ instr 1,<arg>,<addr>
offset_ instr 1,<arg>,<offset>
 
IF addr_ eq 1
arg1 substr <arg>,5,len-4
lea rax,arg1
m=1
 
ELSEIF offset_ eq 1
IF cnt gt 4
arg1 substr <arg>,7,len-6
lea rax,arg1
m=1
ENDIF
ENDIF
 
IF cnt eq 0
IF m eq 0
mov rcx,arg
ELSE
mov rcx,rax
ENDIF
 
ELSEIF cnt eq 1
IF m eq 0
mov rdx,arg
ELSE
MOV rdx,rax
ENDIF
 
ELSEIF  cnt eq 2
IF m eq 0
mov r8,arg
ELSE
mov r8,rax
ENDIF
 
ELSEIF cnt eq 3
IF m eq 0
mov r9,arg
ELSE
mov r9,rax
ENDIF
 
ELSE
IF m eq 1
mov [rsp+(cnt)*8],rax
ELSE
mov qword ptr [rsp+(cnt)*8],arg
ENDIF
ENDIF
endm
 
call function
IF n gt 4
add rsp,(n+k)*8
ELSE
add rsp,20h
ENDIF
endm
Пример использования:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
includelib f:\masm32\lib64\kernel32.lib
include f:\masm32\include64\win64.inc
include f:\masm32\include64\rtl.inc
include f:\masm32\macros\x64.mac
 
extrn CreateProcessA:proc
extrn CloseHandle:proc
 
.data
cmd db 'c:\windows\system32\cmd.exe',0
pi PROCESS_INFORMATION <>
 
.code
start proc uses rbx rdi rsi
local stin:STARTUPINFO
 
invoke CreateProcessA,offset cmd,0,0,0,0,0,0,0,        addr           stin,   offset  pi
 
invoke CloseHandle,pi.hThread
invoke CloseHandle,pi.hProcess
 
ret
start endp
end
Добавлено через 1 минуту
Addr и offset можно использовать как для глобальных переменных, так и для стековых.

Добавлено через 1 час 21 минуту
Макросы, позволяющие обработать исключение на проблемном участке код, устанавливающие и снимающие обработчик SEH. Условие многократного использования одного обработчика исключений - наличие глобальной переменной, для записи смещения, на которое будет передано управление после обработки исключения. Макрос except должен находиться вне процедуры, в которой может возникнуть исключение.

Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
 
try macro
 
push offset Ex_handler
push fs:[0]
mov fs:[0],esp
 
endm
 
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
 
except macro Finally_
Ex_handler::
 
push ebp
mov ebp,esp
 
mov eax,[ebp+16]
mov ecx,Finally_
mov [eax+0b8h],ecx
 
pop ebp
xor eax,eax
ret 16
 
endm
 
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
 
finally macro
 
pop fs:[0]
add esp,4
 
endm
 
; -------------------------------------------------------------------------
Пример использования:

Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
.686
.model flat,stdcall
option casemap:none
 
include \masm32\macros\du.mac
 
.data
 
_finally dd 0
 
.code
assume fs:nothing
start:
 
mov _finally,@f
try
 
xor eax,eax
mov [eax],eax ; здесь будет вызвано исключение, которое будет обработано в процедуре except
 
@@:
finally
 
mov _finally,@f
try
 
xor eax,eax
mov [eax],eax ; здесь будет вызвано исключение, которое будет обработано в процедуре except
 
@@:
finally
 
ret
 
except _finally
end start
5
Not at all!
193 / 190 / 18
Регистрация: 06.10.2013
Сообщений: 360
21.11.2013, 10:26 #39
Тема на одном из форумов натолкнула меня на написание драйвера, позволяющего установить глобальный хук на всё время работы системы до перезагрузки. В данном случае будет изменена функция MessageBoxA из библиотеки user32.dll. Это пример для Windows x32, но и на х64 можно сделать подобное. Требуется только цифровая подпись драйвера.


Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
UNICODE__STRING struct
Length_     dw ?
MaxLength   dw ?
Buffer      dd ?
UNICODE__STRING ends
 
unis macro name,string
local s,e
len sizestr <string>
 
align 4
sstr substr <string>,2,len-2
 
s label word
 
% forc n,<sstr>
dw '&n'
endm
e dw 0
align 4
k=(e-s)
name UNICODE__STRING {k,k+2,s}
endm
 
 
.686P
.model flat,stdcall
option casemap:none
 
include \masm32\include\ntstatus.inc
include \masm32\include\ntddk.inc
include \masm32\include\ntoskrnl.inc
include \masm32\include\native.inc
 
includelib \masm32\lib\ntoskrnl.lib
include \masm32\macros\strings.mac
include \masm32\macros\du.mac
 
.const
 
function db 'MessageBoxA',0
unis ndirectory,'\KnownDlls'
unis ndll,'user32.dll'
fin dd fin_
 
.code
assume fs:nothing
 
DriverUnload proc uses ebx edi esi pdrobject:dword
 
invoke DbgPrint,$CTA0("UNLOADING")
 
mov eax,STATUS_SUCCESS
 
ret
DriverUnload endp
 
; -------------------------------------------------------------------------
 
.code; INIT
 
driver_entry proc uses ebx edi esi pdrobject:dword,pregpath:dword
local oa:OBJECT_ATTRIBUTES
local iob:IO_STATUS_BLOCK
local li:LARGE_INTEGER
local hdll,pmem,hdir,hsec,cbytes:dword
local pspace:dword
 
and pmem,0
and li.LowPart,0
and li.HighPart,0
 
invoke RtlZeroMemory,addr oa,sizeof oa
mov oa._Length,sizeof oa
lea eax,ndirectory
mov oa.ObjectName,eax
mov oa.Attributes,OBJ_KERNEL_HANDLE
 
invoke ZwOpenDirectoryObject,addr hdir,0fh,addr oa   ; получаем описатель директории \KnownDlls
test eax,eax
js @1
invoke DbgPrint,$CTA0("Handle directory 0x%X"),hdir
 
push hdir
pop oa.RootDirectory
lea eax,ndll
mov oa.ObjectName,eax
invoke ZwOpenSection,addr hsec,SECTION_ALL_ACCESS,addr oa       ; открываем секцию user32.dll  
test eax,eax
js @f
 
invoke NtClose,hdir
 
invoke DbgPrint,$CTA0("Handle section 0x%x"),hsec
                                  ; отображаем в адресное пространство своего процесса
invoke ZwMapViewOfSection,hsec,-1,addr pmem,0,li.LowPart,0,addr li,1,0,PAGE_EXECUTE_READWRITE
test eax,eax
js @f
 
push pmem
call get_address
test eax,eax
js @f
 
mov ebx,eax
 
push pmem
call get_space
 
mov pspace,eax
mov eax,(get_space-my_messagebox)
cmp ecx,eax
ja @ok
 
invoke DbgPrint,$CTA0("Free space is too small, fail...")
jmp @un
 
@ok:
try
cli
mov eax,cr0                 ; сбрасываем бит WP в регистре CR0, тем самым разрешая запись 
and eax,0fffeffffh          ; на readonly страницы
mov cr0,eax
sti
 
mov eax,[ebx]                   ; несмотря на то, что запись разрешена, попытка записи без предварительного 
mov byte ptr [ebx],0e8h     ;чтения приведёт  к краху 
mov eax,pspace                 ; правим функцию MessageBoxA
sub eax,5
sub eax,ebx
mov [ebx+1],eax
 
mov edi,pspace
mov eax,[edi]
mov ecx,(get_space-my_messagebox)
mov esi,offset my_messagebox
rep movsb                   ; теперь до перезагрузки системы любой диалог, выданный функцией MessageBoxA
                            ; будет иметь загловок и текст "Hook!"
 
cli
mov eax,cr0
or eax,10000h
mov cr0,eax
sti
 
invoke DbgPrint,$CTA0("MessageBox address 0x%X"),ebx
fin_::
finally
 
@un:
invoke ZwUnmapViewOfSection,-1,pmem            ; закрываем проекцию секции, при этом в ней сохранятся
                                                                      ; все сделанные изменения.
@@:
invoke NtClose,hsec
 
@1:
 
invoke DbgPrint,$CTA0("Last Error 0x%X"),eax
 
mov eax,pdrobject
 
mov (DRIVER_OBJECT ptr [eax]).DriverUnload,offset DriverUnload
 
mov eax,STATUS_SUCCESS
ret
driver_entry endp
 
except fin
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
 
my_messagebox proc
 
call @f
@@:
pop eax
sub eax,offset @b
add eax,offset message
mov [esp+10h],eax
mov [esp+0ch],eax
mov eax,[esp]
add esp,4
push ebp
mov ebp,esp
jmp eax
message db 'Hook!',0
 
my_messagebox endp
 
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
 
get_space proc uses ebx edi esi psection
 
mov ebx,psection
push ebx
 
add ebx,(IMAGE_DOS_HEADER ptr [ebx]).e_lfanew
 
movzx esi,(IMAGE_NT_HEADERS ptr [ebx]).FileHeader.SizeOfOptionalHeader
movzx ecx,(IMAGE_NT_HEADERS ptr [ebx]).FileHeader.NumberOfSections
add esi,ebx
lea esi,[esi+sizeof IMAGE_FILE_HEADER+4]
mov edi,(IMAGE_NT_HEADERS ptr [ebx]).OptionalHeader.SizeOfCode
mov ebx,(IMAGE_NT_HEADERS ptr [ebx]).OptionalHeader.BaseOfCode
 
pop eax
add ebx,eax
invoke DbgPrint,$CTA0("ImageBase 0x%X, Base of Code 0x%X"),psection,ebx
 
@@:
cmp edi,(IMAGE_SECTION_HEADER ptr [esi]).SizeOfRawData
je @f
add esi,sizeof IMAGE_SECTION_HEADER
loop @b
xor eax,eax
jmp @ret
 
@@:
mov esi,(IMAGE_SECTION_HEADER ptr [esi]).Misc.VirtualSize
 
sub edi,esi
add ebx,esi
invoke DbgPrint,$CTA0("Base of space 0x%X, Size 0x%X"),ebx,edi
 
mov eax,ebx
mov ecx,edi
@ret:
ret
get_space endp
 
; -------------------------------------------------------------------------
 
get_address proc uses ebx edi esi psection
 
mov eax,psection
mov ebx,eax
 
add ebx,(IMAGE_DOS_HEADER ptr [eax]).e_lfanew
lea ebx,(IMAGE_NT_HEADERS ptr [ebx]).OptionalHeader
 
mov ebx,(IMAGE_OPTIONAL_HEADER ptr [ebx]).DataDirectory.VirtualAddress
add ebx,eax
 
lea esi,function
mov edi,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfNames
 
push ebx
mov ebx,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).NumberOfNames
 
lea edi,[edi+eax-4]
mov edx,-1
 
@@:
add edi,4
push edi
mov edi,[edi]
add edi,eax
 
inc edx
push esi
mov ecx,sizeof function-1
repe cmpsb
pop esi
pop edi 
je @f
dec ebx
jne @b
pop ebx
mov eax,-1
jmp @ret
 
@@:
pop ebx
mov esi,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfNameOrdinals
shl edx,1
add edx,esi
add edx,eax
movzx edx,word ptr [edx]
mov ebx,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfFunctions
add ebx,eax
shl edx,2
add edx,ebx
mov edx,[edx]
add eax,edx
 
@ret:
ret
get_address endp
end driver_entry
Коды драйверов под х32 и х64, позволяющие найти адрес неэкспортируемой ядром функции, в данных примерах это ZwProtectVirtualMemory. Сначала в библиотеке ntdll.dll находится номер соответствующего системного сервиса, затем, от адреса экспортируемой функции ядра по определённому смещению, фиксированному как в х32, так и в х64 системах проверяются номера системных сервисов до совпадения с требуемым. Имя функции, адрес которой взят за точку отсчёта, определено в дизассембелере. Источник вдохновения - та же тема на том же форуме, название которого здесь нельзя привести.

Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
.686P
.model flat,stdcall
option casemap:none
 
include \masm32\include\ntstatus.inc
include \masm32\include\ntddk.inc
include \masm32\include\ntoskrnl.inc
include \masm32\include\native.inc
 
includelib \masm32\lib\ntoskrnl.lib
include \masm32\macros\strings.mac
include \masm32\macros\du.mac
 
.const
 
nfunction db 'ZwProtectVirtualMemory',0
unis function,'ZwPulseEvent'
unis nfile,'\SystemRoot\system32\ntdll.dll'
 
.code
assume fs:nothing
 
DriverUnload proc uses ebx edi esi pdrobject:dword
 
invoke DbgPrint,$CTA0("UNLOADING")
 
mov eax,STATUS_SUCCESS
 
ret
DriverUnload endp
 
; -------------------------------------------------------------------------
 
.code; INIT
 
driver_entry proc uses ebx edi esi pdrobject:dword,pregpath:dword
local oa:OBJECT_ATTRIBUTES
local iob:IO_STATUS_BLOCK
local li:LARGE_INTEGER
local hfile,pmem,hdo,hsec:dword
 
and pmem,0
and li.LowPart,0
and li.HighPart,0
 
invoke RtlZeroMemory,addr oa,sizeof oa
mov oa._Length,sizeof oa
lea eax,nfile
mov oa.ObjectName,eax
mov oa.Attributes,OBJ_KERNEL_HANDLE
 
invoke ZwCreateFile,addr hfile,FILE_READ_DATA+FILE_EXECUTE,addr oa,addr iob,0,0,FILE_SHARE_READ,FILE_OPEN,0,0,0
test eax,eax
js @1
invoke DbgPrint,$CTA0("Handle file 0x%X"),hfile
 
mov oa.ObjectName,0
invoke ZwCreateSection,addr hsec,SECTION_MAP_EXECUTE+SECTION_MAP_READ,addr oa,0,PAGE_EXECUTE_READ,SEC_IMAGE,hfile
push eax
invoke NtClose,hfile
pop eax
test eax,eax
js @1
 
invoke DbgPrint,$CTA0("Handle section 0x%x"),hsec
 
invoke ZwMapViewOfSection,hsec,-1,addr pmem,0,li.LowPart,0,addr li,1,0,PAGE_EXECUTE_READ
test eax,eax
js @f
 
push pmem
call get_service_number
test eax,eax
js @f
 
mov ebx,[eax]
mov eax,ebx
shr eax,8
 
invoke DbgPrint,$CTA0("Service number 0x%X"),eax
invoke ZwUnmapViewOfSection,-1,pmem
 
call search
test eax,eax
jz @1
invoke DbgPrint,$CTA0("Kernel address 0x%X"),eax
 
@@:
invoke NtClose,hsec
 
@1:
 
invoke DbgPrint,$CTA0("Last Error 0x%X"),eax
 
mov eax,pdrobject
 
mov (DRIVER_OBJECT ptr [eax]).DriverUnload,offset DriverUnload
 
mov eax,STATUS_SUCCESS
ret
driver_entry endp
 
; -------------------------------------------------------------------------
search proc uses ebx edi esi
local psearch:dword
 
invoke MmGetSystemRoutineAddress,addr function
test eax,eax
js @r
 
mov psearch,eax
mov esi,eax
mov edx,14h
mov edi,2
 
@1:
mov ecx,30
 
@@:
mov eax,[esi]
cmp eax,ebx
je @f
add esi,edx
loop @b
 
mov esi,psearch
neg edx
dec edi
jnz @1
 
xor eax,eax
jmp @r
 
@@:
mov eax,esi
@r:
ret
search endp
 
; -------------------------------------------------------------------------
 
get_service_number proc uses ebx edi esi psection
 
mov eax,psection
mov ebx,eax
 
add ebx,(IMAGE_DOS_HEADER ptr [eax]).e_lfanew
lea ebx,(IMAGE_NT_HEADERS ptr [ebx]).OptionalHeader
 
mov ebx,(IMAGE_OPTIONAL_HEADER ptr [ebx]).DataDirectory.VirtualAddress
add ebx,eax
 
lea esi,nfunction
mov edi,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfNames
 
push ebx
mov ebx,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).NumberOfNames
 
lea edi,[edi+eax-4]
mov edx,-1
 
@@:
add edi,4
push edi
mov edi,[edi]
add edi,eax
 
inc edx
push esi
mov ecx,sizeof nfunction-1
repe cmpsb
pop esi
pop edi 
je @f
dec ebx
jne @b
pop ebx
mov eax,-1
jmp @ret
 
@@:
pop ebx
mov esi,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfNameOrdinals
shl edx,1
add edx,esi
add edx,eax
movzx edx,word ptr [edx]
mov ebx,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfFunctions
add ebx,eax
shl edx,2
add edx,ebx
mov edx,[edx]
add eax,edx
 
@ret:
ret
get_service_number endp
end driver_entry
 
;x64
 
include f:\masm32\include64\win64.inc
include f:\masm32\include64\ntddk.inc
include f:\masm32\macros\strings.mac
include f:\masm32\include64\ntdef.inc
include f:\masm32\include64\ntstatus.inc
include f:\masm32\include64\rtl.inc
 
includelib f:\masm32\lib64\ntoskrnl.lib
include f:\masm32\macros\x64.mac
 
extrn DbgPrint:proc
extrn RtlZeroMemory:proc
extrn ZwCreateFile:proc
extrn ZwCreateSection:proc
extrn ZwMapViewOfSection:proc
extrn ZwUnmapViewOfSection:proc
extrn NtClose:proc
extrn MmGetSystemRoutineAddress:proc
 
.const
 
nfunction db 'ZwProtectVirtualMemory',0
unis function,'ZwQuerySection'
unis nfile,'\SystemRoot\system32\ntdll.dll'
 
.code
 
DriverUnload proc uses rbx rdi pdrobject:qword
mov pdrobject,rcx
 
invoke DbgPrint,$CTA0("Unload")
 
mov rax,STATUS_SUCCESS
ret
DriverUnload endp
 
; -------------------------------------------------------------------------
 
.code
 
driver_entry proc uses rbx rdi rsi pdrobject:qword,pregpath:qword
local oa:OBJECT_ATTRIBUTES
local iob:IO_STATUS_BLOCK
local li:LARGE_INTEGER
local hfile:qword
local hsec:qword
local hdo:qword
local pmem:qword
 
mov pdrobject,rcx
mov pregpath,rdx
 
and pmem,0
and li.LowPart,0
and li.HighPart,0
 
invoke RtlZeroMemory,addr oa,sizeof oa
mov oa._Length,sizeof oa
lea rax,nfile
mov oa.ObjectName,rax
mov oa.Attributes,OBJ_KERNEL_HANDLE
 
invoke ZwCreateFile,addr hfile,FILE_READ_DATA+FILE_EXECUTE,addr oa,addr iob,0,0,FILE_SHARE_READ,FILE_OPEN,0,0,0
test eax,eax
js @1
invoke DbgPrint,$CTA0("Handle file 0x%X"),hfile
 
mov oa.ObjectName,0
invoke ZwCreateSection,addr hsec,SECTION_MAP_EXECUTE+SECTION_MAP_READ,addr oa,0,PAGE_EXECUTE_READ,SEC_IMAGE,hfile
push rax
 
invoke NtClose,hfile
pop rax
test eax,eax
js @1
 
invoke DbgPrint,$CTA0("Handle section 0x%x"),hsec
 
invoke ZwMapViewOfSection,hsec,-1,addr pmem,0,li.QuadPart,0,addr li,1,0,PAGE_EXECUTE_READ
test eax,eax
js @f
 
invoke DbgPrint,$CTA0("Memory address 0x%X"),pmem
mov rcx,pmem
sub rsp,20h
call get_service_number
add rsp,20h
 
test eax,eax
js @f
 
add eax,3
mov ebx,[eax]
mov eax,ebx
shr eax,8
 
invoke DbgPrint,$CTA0("Service number 0x%X"),rax
invoke ZwUnmapViewOfSection,-1,pmem
 
sub rsp,20h
call search
add rsp,20h
test eax,eax
jz @1
mov rbx,rax
shr rax,32
invoke DbgPrint,$CTA0("ZwProtectVirtualMemory address 0x%X%X"),rax,rbx
 
@@:
invoke NtClose,hsec
 
@1:
 
invoke DbgPrint,$CTA0("Last Error 0x%X"),rax
 
mov rax,pdrobject
lea rcx,DriverUnload
mov (DRIVER_OBJECT ptr [rax]).DriverUnload,rcx
mov rax,STATUS_SUCCESS
ret
driver_entry endp
 
; -------------------------------------------------------------------------
search proc uses rbx rdi rsi
local psearch:qword
 
invoke MmGetSystemRoutineAddress,addr function
test eax,eax
jz @r
push rax
mov rcx,rax
shr rax,32
invoke DbgPrint,$CTA0("ZwQuerySection address 0x%X%X"),rax,rcx
 
pop rax
add rax,14h
 
mov psearch,rax
mov rsi,rax
mov rdx,20h
mov rdi,2
 
@1:
mov rcx,50
 
@@:
mov rax,[rsi]
cmp ax,bx
je @f
add rsi,rdx
loop @b
 
mov rsi,psearch
neg rdx
dec rdi
jnz @1
 
xor eax,eax
jmp @r
 
@@:
mov rax,rsi
sub rax,14h
@r:
ret
search endp
 
; -------------------------------------------------------------------------
 
get_service_number proc uses rbx rdi rsi psection:qword
mov psection,rcx
mov rax,rcx
mov rbx,rax
 
and rdi,0
add ebx,(IMAGE_DOS_HEADER ptr [rax]).e_lfanew
lea rbx,(IMAGE_NT_HEADERS ptr [ebx]).OptionalHeader
 
mov ebx,(IMAGE_OPTIONAL_HEADER ptr [rbx]).DataDirectory.VirtualAddress
add ebx,eax
 
lea rsi,nfunction
mov edi,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfNames
 
push rbx
and rbx,0ffffffffh
mov ebx,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).NumberOfNames
 
lea edi,[edi+eax-4]
mov rdx,-1
 
@@:
add rdi,4
push rdi
mov edi,[rdi]
add edi,eax
 
inc edx
push rsi
mov ecx,sizeof nfunction-1
repe cmpsb
pop rsi
pop rdi 
je @f
 
dec ebx
jne @b
pop rbx
mov eax,-1
jmp @ret
 
@@:
pop rbx
mov esi,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfNameOrdinals
shl rdx,1
add edx,esi
add edx,eax
movzx edx,word ptr [edx]
mov ebx,(IMAGE_EXPORT_DIRECTORY ptr [ebx]).AddressOfFunctions
add ebx,eax
shl edx,2
add edx,ebx
mov rdx,[edx]
add eax,edx
 
@ret:
ret 
get_service_number endp
end

Макрос unis для х64.
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
UNICODE__STRING struct
Length_     dw ?
MaxLength   dw ?
Reserve     dd 0
Buffer      dq ?
UNICODE__STRING ends
 
unis macro name,string
local s,e
len sizestr <string>
 
align 4
sstr substr <string>,2,len-2
 
s label word
 
% forc n,<sstr>
dw '&n'
endm
e dw 0
align 8
k=(e-s)
name UNICODE__STRING {k,k+2,0,s}
endm
4
Mikl___
Автор FAQ
11376 / 5919 / 535
Регистрация: 11.11.2010
Сообщений: 10,937
17.12.2013, 06:18  [ТС] #40
  1. Встроенный ассемблер реализует
    • команды для i8086 по умолчанию
    • команды i286 при наличии директивы {$G+}
    • команды сопроцессора при наличии директив {$G+} {$N+} {$E+}
    • 32-разрядные команды не поддерживаются
  2. для всех переменных, меток и комментариев используется синтаксис языка Pascal
  3. к встроенному ассемблеру обращаются через оператор
    Pascal
    1
    2
    3
    
    ASM
     команды ассемблера
    END;
  4. встроенный ассемблер не может (не должен) изменять содержимое регистров BP, SP, SS и DS
  5. встроенный ассемблер поддерживает только директивы DB, DW, DD
  6. все выражения вычисляются как 32-разрядные целые от -2147483648 до 4294967295
  7. значения выражений в вещественой форме не поддерживается
  8. Pascal воспринимает числа только в десятичной системе счисления или в шестнадцатеричном виде (префикс $)
  9. строковые константы в стиле языка Паскаль
  10. интерпретация параметра var как 32-разрядного указателя es:[регистр]. Требуется сначала параметр var командой les загрузить в регистр
    Assembler
    1
    
    les регистр,var-параметр
    а потом использовать как es:[регистр]
  11. в паскале есть директива assembler, которая сравнима с директивой external. Эта директива позволяет строить на Паскале ассемблерные процедуры по тем же правилам, что и внешние процедуры и функции
Например, требуется реализовать функцию Sum=x+y, переменные Sum, x и y определены как integer используем специальную переменную Паскаля @Result для возврата из функции значений
  1. на "чистом" паскале
    Pascal
    1
    2
    3
    4
    5
    6
    
    var 
    x,y,s : integer
    Function Sum (x,y:integer):integer;
    Begin
     Sum := x+y;
    End;
  2. передача параметров по значению
    Pascal
    1
    2
    3
    4
    5
    6
    7
    8
    
    Function Sum (x,y:integer):integer;
    begin
     asm
       mov ax,x
       add ax,y
       mov @Result,ax
     end;
    end;
  3. передача параметров по ссылке
    Pascal
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    Function Sum (var x,y:integer):integer;
    begin
     asm
      les di,x
      les bx,y
      mov ax,es:[di]  
      add ax,es:[bx]
       mov @Result,ax
     end;
    end;
  4. ассемблерная функция
Pascal
1
2
3
4
5
Function Sum (x,y:integer):integer; assembler;
asm
  mov ax,x
  add ax,y
end;
результат
Pascal
1
2
3
4
5
6
7
8
begin
 writeln('Вычислить: Sum = x + y для данных типа integer');
 write('Введите значение x');
 read(x);
 write('Введите значение y');
 read(y);
 s:=Sum(x,y);
 writeln(' ',x, ' + ',y,' = ',s);
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int FuncAsmI (int x)// эквивалент С-функции int FuncCI (int x) { return x/3;}
{ asm
   { mov ax,x
      cwd
      mov cx,3
      idiv cx
    }
}
unsigned int funcAsmU (unsigned int x)// эквивалент С-функции int FuncCu (unsigned int x) { return x/3;}
{ asm
   { mov ax,x
      xor dx,dx
      mov cx,3
      div cx
   }
}
  1. учитывайте, что имеете дело с 32-разрядной системой int — 32-разряда, short int — 16-разрядов
  2. оператор asm должен быть с двумя поддчерками __asm
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
short int FuncAsmI (short int x)
{ __asm
   { mov ax,x
      cwd
      mov cx,3
      idiv cx
    }
}
unsigned short int funcAsmU (unsigned short int x)
{ __asm
   { mov ax,x
      xor dx,dx
      mov cx,3
      div cx
   }
}
пример ассемблерной вставки, складывающей два числа
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
main()// обычный код на C++
{      int a = 1; // объявляем переменную a и кладем туда значение 1
        int b = 2; // объявляем переменную a и кладем туда значение 2
        int c;     // объявляем переменную c, но не инициализируем ее
        // а тут ассемблерная вставка
        __asm{
                mov eax, a   // загружаем значение переменной a в регистр EAX
                add eax, b // складываем a с b, записывая результат в EAX
                mov c, eax   // загружаем значение EAX в переменную c
        } // конец ассемблерной вставки
        // а потом снова обычный код на C++
        // выводим содержимое c на экран с помощью Сишной функции printf
        printf("a + b = %x + %x = %x\n", a, b, c);
}
Или, что то же самое, так:
C++
1
2
3
4
// ассемблерная вставка
__asm mov eax, a
__asm add eax, b
__asm mov c, eax
А можно вообще в одну строчку:
C++
1
2
// ассемблерная вставка
__asm mov eax, a __asm add eax, b __asm mov c, eax
Во встроенном ассемблере Visual C++ можно использовать все инструкции вплоть до Pentium 4 и AMD Athlon. Поддерживается также MMX. Поддержки Itanium и x64 пока нет .

По синтаксису встроенный ассемблер Visual C++ частично совпадает с MASM. Например, как и в MASM можно строить выражения с операндами и использовать offset с глобальными переменными. Как и в MASM, можно использовать глобальные метки и принудительно определять короткие переходы. Однако определить локальную метку с помощью @@ во встроенном ассемблере Visual C++ не получится – замена lab: на @@lab: в предыдущем примере вызовет ошибку компиляции. Есть и другие отличия от MASM. Например, во встроенном ассемблере Visual C++ нет никаких средств для объявления переменных, поэтому о привычных DB, DW, DD, DQ, DT, DF, DUP и THIS можно забыть. Зато можно использовать переменные, объявленные в программе на C++.

Во встроенном ассемблере также нельзя объявлять структуры — директивы STRUC, RECORD, WIDTH и MASK недопустимы. Вместо этого можно использовать структуры, объявленные в программе на C++. Кроме того, в ассемблере можно использовать комментарии и HEX-числа в стиле C++.

В принципе, все отличия подробно описаны в MSDN (идет в комплекте с Visual Studio).

Обычно в Visual C++ ассемблер используется для написания функций. Как правило, функции, написанные на ассемблере, объявляют с использованием директивы _stdcall. В этом случае параметры передаются в функцию на стеке в обратном порядке, а результат работы возвращается в eax.

Для примера рассмотрим функцию, которая находит длину ASCIIZ-строки:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int _stdcall get_str_length(char *inputstr)
{// чаще всего функция целиком состоит из одной большой ассемблерной вставки
__asm{
; можно свободно обращаться к переменным, переданным в функцию
    mov edi, inputstr
    mov esi, edi
    or ecx, -1
    xor al, al
    cld
    repne scasb
    sub edi, esi
; результат работы функции следует возвращать в eax
    mov eax, edi
    dec eax
   }
}
Использовать эту функцию можно так:
C++
1
2
3
4
5
6
7
int main()
{
   char str_1[]="Hello, world!";
   int i;
   i = get_str_length(str_1);
   printf("String: %s\nLength: %d", str_1, i); 
}
При написании кода на ассемблере Visual C++ следует помнить некоторые моменты. Во-первых, значения регистров не передаются между ассемблерными вставками. Например, если в ассемблерной вставке ты установил eax в 1, то в следующей ассемблерной вставке eax не обязательно будет равно 1.
Во-вторых, нужно быть осторожным с регистрами и стеком. В середине ассемблерных вставок нельзя менять регистры ds, ss, sp, bp и флаги. Если эти регистры все-таки меняются, перед выходом из ассемблерной вставки их нужно обязательно восстановить. Что касается стека, то тут нужно соблюдать правило, которое гласит: если в ассемблерной вставке нечто ложится на стек, то в той же ассемблерной вставке это «нечто» должно со стека сниматься.
Рассмотрим, например, такой код:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
// наша функция на ассемблере
void _stdcall Test()
{
__asm{
; кладем на стек eax
push eax
 
; перед тем как ассемблерная вставка кончится, нужно
; снять eax со стека (например, с помощью pop), но мы забыли это сделать ;)
         }
}
int main()
{ 
 
// вызываем функцию... и любуемся сообщением об ошибке :)
Test();
}
И, наконец, если пишется не драйвер, а обычное Win32-приложение, в ассемблерной вставке не должно быть привилегированных инструкций. Тем, кто хочет узнать больше — почитайте MSDN — там есть вся необходимая информация.
6
17.12.2013, 06:18
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
17.12.2013, 06:18
Привет! Вот еще темы с решениями:

Подскажите книгу для программирования под Win32, MASM/TASM с большим количеством примеров
Ув. форумчане, подскажите пожалуйста какую-нибудь обучающую книгу для...

TASM to MASM
Компилил стареньким ТАСМом вот такой вот фрагмент: ;обычное сложение 2-х чисел...

MASM,TASM
Очевидно, что программирование на макроассемблере легче для восприятия чем на...

FASM/MASM/TASM
В ентернетах очень много различных примеров для различных ассемблеров, я решил...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
40
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru