SAS Emulator (эмулятор «ПК-01 Львов»)

...
  • Pathoswithin wrote: По поводу производительности, в простейшем случае, чтобы вызывать цикл 50 раз в секунду, можно добавить в него такой код:
    mov eax, 26
    mov ebx, 9
    int 40
    mov ebx, eax
    shr ebx, 1
    inc ebx
    shl ebx, 1
    sub ebx, eax
    mov eax, 5
    int 40
    Pathoswithin, как я этот код только не примудривал, - ошибку выдает Extr_KlbrInWin
    Image
    а QEMU, сразу же закрывает после запуска! :-(
    Пробовал вставлять над своими процедурами, и под ними... и с pusha/popa, и в отдельной процедуре.... и в отдельной процедуре с pusha/popa... и в пустом цикле без своих процедур вообще... и опять же с pusha/popa и без этого, а результат один и тот же, - ОШИБКА! :-(
    Надо еще паузу саму-по-себе будет попробовать будет
  • А, int 40h же. Короче, mcall лучше использовать...
  • ALEXS1983 wrote:Да и вообще, я ничего не понял с теми процедурами. Я думал это как-то проще. А как потом доступатся к той памяти ? Ну к нормальной понятно например mov [ebx+VideoDirtyM],al или mov bl,[VideoDirtyM+eax] А быстродеqствие такое же как и в обычной размеченной, типа VideoDirtyM rb 16384 ?
    Всё то же. Это частичные аналоги функций API, в одной нехорошей системе: HeapAlloc, HeapRealloc и т.д.

    Включение в главный файл:

    Code: Select all

    include "proc32.inc" ; макросы облегчают жизнь ассемблерщиков!
    include "memory.inc" ; макросы облегчают жизнь ассемблерщиков!
    Использование функций:

    Code: Select all

    ; например, нужен очищенный буфер, размером в 64кб (на выходе: eax=адрес буфера).
    stdcall  memory.initialize_allocate_reallocate_and_clear, 64000

    Code: Select all

    ; например, нужен доступ к 100-ому элементу в буфере (адрес буфера, в регистре eax).
    mov     [eax+100], dword 0x12345678

    Code: Select all

    ; например, нужно удалить выделенный буфер (eax=адрес буфера).
    stdcall  memory.free, eax
    Вот, закомментировал и переделал макросы по управлению блоком(ами) памяти (буфера, массива, кучи), который выкладывал ранее.
    Attachments
    memory.inc (3.7 KiB)
    Downloaded 290 times
  • Вот, небольшое сравнение. Может так, будет понятней.

    Выделение динамического массива (Delphi):

    Code: Select all

    1. m: array of integer;  // определение динамического массива.
    2. SetLength(m, 4096);   // перераспределение динамического массива.
    3. MemFree(m);           // удаление динамического массива.
    Выделение динамического массива (с использованием процедур из memory.inc)(FASM):

    Code: Select all

    1. stdcall memory.initialize_and_allocate, 1024  ; определение динамического массива.
       ; проверяем, хватает ли памяти в ОЗУ, для выделения динамического массива (0-неуспех, иначе размер созданной кучи).
       test  eax, eax
       jz    error.not_enough_memory
    2. stdcall memory.reallocate, 4096, eax          ; перераспределение динамического массива.
    3. stdcall memory.free, eax                      ; удаление динамического массива.
    Last edited by Yason on Fri Mar 25, 2016 5:04 pm, edited 2 times in total.
  • Yason,
    Ну вот смотри как было и как я заменил. А НЕ РАБОТАЕТ ВЕДЬ! :-(

    Code: Select all

    БЫЛО:
    VideoDirtyM rb 16385
    …......................
    …......................
    mov [ebx+VideoDirtyM],al
    …......................
    …......................
    mov [eax+VideoDirtyM],bl
    …......................
    …......................
    mov cl,[VideoDirtyM+ebx]
    …......................
    …......................
    
    ЗАМЕНИЛ ВЕЗДЕ ВОТ ТАК (СООТВЕТСВЕННО)
    
    СТАЛО:
    VideoDirtyMa rd 1
    …......................
    …......................
     INITuMem: ; заупскается при старте 
     stdcall  memory.initialize_allocate_reallocate_and_clear, 16385
     mov[VideoDirtyMa],eax
     ret
    …......................
    …......................
        add ebx,[VideoDirtyMa]
        mov [ebx],al 
    …......................
    …......................
    add eax,[VideoDirtyMa]
        mov [eax],bl
    ….....................
    ….....................
    add ebx,[VideoDirtyMa]
     mov cl,[ebx]
    ….....................
    ….....................
    Где "собака зарыта" ?
  • ALEXS1983 wrote:Yason,
    Ну вот смотри как было и как я заменил. А НЕ РАБОТАЕТ ВЕДЬ! :-(
    Мои процедуры, работают как часы - проверил. Просто ты, что-то не так делаешь. Адресуешься наверно, не верно. Твой код, вообще компилируется?
  • Yason wrote:Мои процедуры, работают как часы - проверил. Просто ты, что-то не так делаешь. Адресуешься наверно, не верно. Твой код, вообще компилируется?
    "Адресуешься наверно, не верно"
    Ну я ведь выложил что я заменял, ну и что там не так ?!....
    вот и я думаю, что всё так, а работать то не хочет.
    Ну да ладно. Я на этом не сильно парюсь, после того как я узнал и попробовал KPACK :-)

    "Твой код, вообще компилируется?" -
    Да, компилируется! А после запуска в KlbrInWin выдаёт табличку, вот такую, как тут viewtopic.php?f=43&t=3253&start=30#p64936 ну понятное дело, что с другими цифрами.
    Я предполагаю, что ты можешь сказать, что в KlbrInWin нефиг запускать и проверять что-то....
    Но дело в том, что эмуль до сих пор работал в КОС, QEMU и KlbrInWin, поэтому пусть он будет большим размером, но как и прежде запускается и работает везде! :-) Это очень удобно! :-) И в этом преимущество эмуля, перед теми программами которые где-то не запускаются.
    В КОС и QEMU я не запускал, так как не сразу сообразил проверить, а когда сообразил уже "откатал" назад код.
    Last edited by ALEXS1983 on Fri Mar 25, 2016 4:43 pm, edited 1 time in total.
  • Сегодня занимался изучением и наблюдением за поведением эмулятора и загрузки процессора компа, используя различные процедуры задержек в эмуляторе. Наблюдал в KOS, KlbrInWin и QEMU. Можно долго и нудно объяснять, но проще выражаясь, пришлось реализовать разные способы запуска движка эмуля, с различными процедурами задержек, для разных случаев. Забегая наперёд, пришёл к выводу, что эмуль, сам-по-себе без встроенной игры делать никогда не буду и что обязательно нужен генератор, в котором, нужно регулировать количество тактов КР580 в одном цикле (opcodes_to_run), номер движка с которым будет запущен емуль (TimerNumber), и задержка которая будет использоваться в том движке эмуля (TimerDelay), для нормальной работы конкретной игры в конкретном случае (в KOS, KlbrInWin или QEMU), да и на конкретной машине. Так же у меня возникли большие сомнения в возможности реализации режима 512х512, точнее говоря, нормальной его работы, хотя бы в одном из перечисленных случаев (в KOS, KlbrInWin или QEMU).

    Выложу часть кода с реализацией движков и задержек, может кто-то подскажет ещё какие-то варианты реализации движков и задержек.
    Spoiler:

    Code: Select all

    opcodes_to_run: dd 55000 ; 68500
    TimerNumber: dd 3 ; номер движка таймера 
    TimerDelay: dd 2 ; задержка
    ;---------------------------------------------------------------------
    
    EmulEngineTimer0: ; основной движок без таймера
           mov eax,[opcodes_to_run]
           mov [i8080_do_opcodes_nb_cycles],eax
           call i8080_do_opcodes
          call draw_screen
                ret
                
    EmulEngineTimer1:
          Call EmulEngineTimer0
          call Timer1
            ret            
               
    
    EmulEngineTimer2:
    Call Timer2
    mov eax,[Timer2_result]
    Cmp eax, [PlatoonTimer2T]
    jb EmulEngineTimer2_ret
    call PlatoonTimer2 ; взвод таймера
          Call EmulEngineTimer0
    EmulEngineTimer2_ret:   ret            
    
    EmulEngineTimer3:
          Call EmulEngineTimer0
          call Timer3
            ret 
    …..........................
    …..........................
    macro INT0x40 { int 0x40 }
    Timer1:
    mov eax, 23 ; - номер функции
    mov ebx, [TimerDelay];ebx = таймаут (в сотых долях секунды)
    INT0x40;
    ret
    ;---------------------------------------------------------------------
    Timer2_result rd 1
    Timer2:
    mov eax,26
    mov ebx,9
    int 0x40
    mov [Timer2_result],eax ;= число сотых долей секунды, прошедших с момента
    ret
    
     PlatoonTimer2T rd 1
     PlatoonTimer2: ; взвод таймера (для блока комманд)
    Call Timer2;
    mov eax,[Timer2_result]
    add eax,2
    mov [PlatoonTimer2T],eax
    ret
    ;---------------------------------------------------------------------
    Timer3:
    mov eax, 26 ; Функция 26, подфункция 9 - получить значение счётчика времени.
    mov ebx, 9 ;
    mcall ;int 40 Возвращаемое значение:  * eax = число сотых долей секунды, прошедших с момента запуска системы
    mov ebx, eax
    shr ebx,1
    inc ebx
    shl ebx,1 ; ebx = время в сотых долях секунды
    sub ebx,eax ;  eax = 5 - номер функции
    mov eax,5 ; Функция 5 - пауза
    mcall;int 40
    ret
    
    ====================================================
    Spoiler:==============================================================
    ============== Функция 2 - получить код нажатой клавиши. =============
    ======================================================================
    Забирает код нажатой клавиши из буфера.
    Параметры:
    * eax = 2 - номер функции
    Возвращаемое значение:
    * если буфер пуст, возвращается eax=1
    * если буфер непуст, то возвращается al=0, ah=код нажатой клавиши,
    биты 16-23 содержат сканкод нажатой клавиши в режиме ASCII,
    в режме сканкодов биты обнулены.
    биты 23-31 обнулены
    * если есть "горячая клавиша", то возвращается
    al=2, ah=сканкод нажатой клавиши (0 для управляющих клавиш),
    старшее слово регистра eax содержит состояние управляющих клавиш
    в момент нажатия горячей клавиши
    Замечания:
    * Существует общесистемный буфер нажатых клавиш размером 120 байт,
    организованный как очередь.
    * Существует ещё один общесистемный буфер на 120 "горячих клавиш".
    * При вызове этой функции приложением с неактивным окном
    считается, что буфер нажатых клавиш пуст.
    * По умолчанию эта функция возвращает ASCII-коды; переключиться на
    режим сканкодов (и назад) можно с использованием функции 66.
    Однако, горячие клавиши всегда возвращаются как сканкоды.
    * Узнать, какие комбинации клавиш соответствуют каким кодам, можно,
    запустив приложения keyascii и scancode.
    * Сканкоды возвращаются непосредственно клавиатурой и фиксированы;
    ASCII-коды получаются с использованием таблиц преобразования,
    которые можно установить подфункцией 2 функции 21 и прочитать
    подфункцией 2 функции 26.
    * Как следствие, ASCII-коды учитывают текущую раскладку клавиатуры
    (rus/en) в отличие от сканкодов.
    * Поступает информация только о тех горячих клавишах, которые были
    определены этим потоком подфункцией 4 функции 66.

    ---------------------- Константы для регистров: ----------------------
    eax - SF_GET_KEY (2)
    ======================================================================
    Может мне кто нибудь всё таки скажет, как после выполнения этой функции определять дополнительные клавиши (ext и ext2 или как там их правильно) ?
  • ALEXS1983 wrote:после запуска в KlbrInWin выдаёт табличку, вот такую, как тут viewtopic.php?f=43&t=3253&start=30#p64936 ну понятное дело, что с другими цифрами.
    Я предполагаю, что ты можешь сказать, что в KlbrInWin нефиг запускать и проверять что-то....
    Так и есть. KlbrInWin очень сильно устарел и многих новых функций API Колибри, в нём нет. Поэтому, нефиг его использовать. Хотя процедуры выделения и удаления массива работают - проверил (никаких ошибок).
  • ALEXS1983 wrote:ImageData = 262144 байт ( ImageData rd (CanvasWidth * CanvasHeight) ; сюда рисовать перед выводом на экран), в принципе он полностью нулевой, точнее должен быть нулевой, :-) значит пресуется "на ура"!
    Похоже ты не в курсе: если располагать пустой буфер в самом конце программы, то он вообще не будет занимать место в исполняемом файле.
    ALEXS1983 wrote:как после выполнения этой функции определять дополнительные клавиши
    Есть функция 66.4 для установки комбинаций клавиш, и функция 66.3 для получения управляющих клавиш непосредственно.
  • Pathoswithin wrote:Похоже ты не в курсе: если располагать пустой буфер в самом конце программы, то он вообще не будет занимать место в исполняемом файле.
    Не знал! :shock:
    А ведь точно! :-)
    Pathoswithin wrote:Есть функция 66.4 для установки комбинаций клавиш, и функция 66.3 для получения управляющих клавиш непосредственно.
    Ну ужас какой-то!....
    Pathoswithin ну а как же оно здесь без этих функций....
    Spoiler:

    Code: Select all

    ;
    ;   KEYBOARD SCANCODE EXAMPLE
    ;
    ;   Compile with FASM for Menuet
    ;
    
    include "lang.inc"
    include "macros.inc"
    
        use32
        org    0x0
    
        db     'MENUET01'  ; 8 byte id
        dd     0x01        ; header version
        dd     START       ; start of code
        dd     I_END       ; size of image
        dd     0x1000     ; memory for app
        dd     0x1000     ; esp
        dd     0x0 , 0x0   ; I_Param , I_Icon
    
    START:                 ; start of execution
    
        mov  eax,66    ; keyboard mode definitions
        mov  ebx,1     ; set
        mov  ecx,1     ; return scancodes
        mcall
    
        mov  eax,26    ; get setup for keyboard
        mov  ebx,2
        mov  ecx,1     ; base keymap
        mov  edx,keymap
        mcall
    
        mov  eax, 48                   ; GET SYSTEM COLORS
        mov  ebx, 3
        mov  ecx, sc
        mov  edx, sizeof.system_colors
        mcall
    
      red:
        call draw_window
    
    still:
    
        mov  eax,10                 ; wait here for event
        mcall
    
        cmp  eax,1                  ; redraw request ?
        je   red
        cmp  eax,2                  ; key in buffer ?
        je   key
        cmp  eax,3                  ; button in buffer ?
        je   button
    
        jmp  still
    
    
      key:                          ; key
        mov  eax,2                  ; just read it and ignore
        mcall
    
        mov  esi,scan_codes+1
        mov  edi,scan_codes+0
        mov  ecx,15
        cld
        rep  movsb
    
        mov  esi,key_codes+12
        mov  edi,key_codes+0
        mov  ecx,15*12
        cld
        rep  movsb
    
        shr  eax,8                   ; scancode
        and  eax,0xff
        mov  [scan_codes+15],al
    
        mov  [key_codes+15*12+8],dword 'Down'
        cmp  eax,128
        jb   no_up
        mov  [key_codes+15*12+8],dword 'Up  '
      no_up:
    
        mov    ebx,eax
        and    ebx,0x7f
    
        movzx  edx,byte [keymap+ebx]  ; key from keymap
        mov    [key_codes+15*12+0],edx
        mov    [key_codes+15*12+4],dword '    '
    
        movzx  edx,byte [ext]
        shl    edx,8
        add    ebx,edx
    
        mov    esi,ext0-10
      new_ext0:
        add    esi,10
        cmp    esi,ext0end
        jg     exit_ext0
        movzx  edx,word [esi]
        cmp    edx,ebx
        jne    new_ext0
        mov    edx,[esi+2]
        mov    [key_codes+15*12+0],edx
        mov    edx,[esi+6]
        mov    [key_codes+15*12+4],edx
      exit_ext0:
      no_ext_off:
    
        cmp  [ext2],0
        je   noext2dec
        dec  [ext2]
        jne  noext2dec
        mov    [key_codes+15*12+0],dword '----'
        mov    [key_codes+15*12+4],dword '----'
      noext2dec:
    
        mov  [ext],0
    
        cmp  eax,224
        jne  no_ext
        mov  [key_codes+15*12+0],dword '    '
        mov  [key_codes+15*12+4],dword '    '
        mov  [key_codes+15*12+8],dword 'Ext '
        mov  [ext],1
      no_ext:
    
        cmp  eax,225
        jne  no_ext2
        mov  [key_codes+15*12+0],dword '    '
        mov  [key_codes+15*12+4],dword '    '
        mov  [key_codes+15*12+8],dword 'Ext2'
        mov  [ext],2
        mov  [ext2],2
      no_ext2:
    
    
        call draw_codes
        jmp  still
    
    
    
      button:                       ; button
        or   eax, -1                ; close this program
        mcall
    
    
    
    
    ;   *********************************************
    ;   *******  WINDOW DEFINITIONS AND DRAW ********
    ;   *********************************************
    
    
    draw_window:
    
    
        mov  eax,12                    ; function 12:tell os about windowdraw
        mov  ebx,1                     ; 1, start of draw
        mcall
    
                                       ; DRAW WINDOW
        mov  eax, 0                    ; function 0 : define and draw window
        mov  ebx, 100*65536+200        ; [x start] *65536 + [x size]
        mov  ecx, 100*65536+275        ; [y start] *65536 + [y size]
        mov  edx, [sc.work]            ; color of work area RRGGBB,8->color gl
        or   edx, 0x34000000
        mov  edi, title                ; WINDOW LABEL
        mcall
                                       
        mov  eax, 4
        mov  ebx, 15*65536+10
        xor  ecx, ecx
        mov  edx, text
        mov  esi, text.len
        mcall
    
        call draw_codes
    
        mov  eax,12                    ; function 12:tell os about windowdraw
        mov  ebx,2                     ; 2, end of draw
        mcall
    
        ret
    
    
    draw_codes:
    
        mov  eax,47
        mov  ebx,6*65536
        mov  edx,15*65536+35
        mov  edi,0
        mov  esi,0
      newscan:
        pusha
        mov  cx,dx
        shl  ecx,16
        add  ecx,10
        mov  eax,13   ; filled rectangle
        mov  ebx,15*65536+160
        mov  edx,[sc.work]
        mcall
        popa
        pusha
        mov  ebx,edx
        add  ebx,70*65536
        mov  eax,4    ; text
        mov  ecx,[sc.work_text]
        mov  edx,key_codes
        imul edi,12
        add  edx,edi
        mov  esi,12
        mcall
        popa
        movzx  ecx,byte [scan_codes+edi]
        mcall     ; number
        inc  ecx
        add  edx,12
        inc  edi
        cmp  edi,16
        jne  newscan
    
        ret
    
    
    ; DATA AREA
    
    ext0:
    
        db    1,0,'Esc     '
        db   28,0,'Enter   '
        db   29,0,'L-Ctrl  '
        db   41,0,'1/2     '
        db   42,0,'L-Shift '
        db   54,0,'R-Shift '
        db   55,0,'Num *   '
        db   56,0,'Alt     '
        db   58,0,'CapsLck '
        db   59,0,'F1      '
        db   60,0,'F2      '
        db   61,0,'F3      '
        db   62,0,'F4      '
        db   63,0,'F5      '
        db   64,0,'F6      '
        db   65,0,'F7      '
        db   66,0,'F8      '
        db   67,0,'F9      '
        db   68,0,'F10     '
        db   69,0,'NumLock '
        db   70,0,'SclLock '
        db   71,0,'Num 7   '
        db   72,0,'Num 8   '
        db   73,0,'Num 9   '
        db   74,0,'Num -   '
        db   75,0,'Num 4   '
        db   76,0,'Num 5   '
        db   77,0,'Num 6   '
        db   78,0,'Num +   '
        db   79,0,'Num 1   '
        db   80,0,'Num 2   '
        db   81,0,'Num 3   '
        db   82,0,'Num 0   '
        db   83,0,'Num ,   '
        db   87,0,'F11     '
        db   88,0,'F12     '
    
        db   28,1,'Num Ent '
        db   29,1,'R-Ctrl  '
        db   53,1,'Num /   '
        db   55,1,'PrScr   '
        db   56,1,'Alt Gr  '
        db   71,1,'Home    '
        db   72,1,'Up-A    '
        db   73,1,'PgUp    '
        db   75,1,'Left-A  '
        db   77,1,'Right-A '
        db   79,1,'End     '
        db   80,1,'Down-A  '
        db   81,1,'PgDown  '
        db   82,1,'Insert  '
        db   83,1,'Delete  '
        db   91,1,'Menu-1  '
        db   92,1,'Menu-2  '
        db   93,1,'Menu-3  '
    
        db   29,2,'Break   '
    
    ext0end:
    
    
    if lang eq ru
      text:
          db 'СЧИТЫВАЮ ДАННЫЕ С КЛАВИАТУРЫ'
      .len = $ - text
    
      title      db   'СКАНКОДЫ КЛАВИАТУРЫ',0
    else
      text:
          db 'READING RAW SCANCODE DATA'
      .len = $ - text
    
      title      db   'KEYBOARD SCANCODES',0
    end if
    
    ext  db 0x0
    ext2 db 0x0
    
    pos dd 0x0
    
    I_END:
    
    sc system_colors
    
    scan_codes: times 16 db ?
    
    key_codes:  times 16 dd ?,?,?
    
    keymap:
    
    
    Я всё никак не могу сосредоточится и добраться до истины в этом коде.
    Знаю что где-то вот в этом моменте, но никак не могу понять что да как
    Spoiler:

    Code: Select all

      key:                          ; key
        mov  eax,2                  ; just read it and ignore
        mcall
    
        mov  esi,scan_codes+1
        mov  edi,scan_codes+0
        mov  ecx,15
        cld
        rep  movsb
    
        mov  esi,key_codes+12
        mov  edi,key_codes+0
        mov  ecx,15*12
        cld
        rep  movsb
    
        shr  eax,8                   ; scancode
        and  eax,0xff
        mov  [scan_codes+15],al
    
        mov  [key_codes+15*12+8],dword 'Down'
        cmp  eax,128
        jb   no_up
        mov  [key_codes+15*12+8],dword 'Up  '
      no_up:
    
        mov    ebx,eax
        and    ebx,0x7f
    
        movzx  edx,byte [keymap+ebx]  ; key from keymap
        mov    [key_codes+15*12+0],edx
        mov    [key_codes+15*12+4],dword '    '
    
        movzx  edx,byte [ext]
        shl    edx,8
        add    ebx,edx
    
        mov    esi,ext0-10
      new_ext0:
        add    esi,10
        cmp    esi,ext0end
        jg     exit_ext0
        movzx  edx,word [esi]
        cmp    edx,ebx
        jne    new_ext0
        mov    edx,[esi+2]
        mov    [key_codes+15*12+0],edx
        mov    edx,[esi+6]
        mov    [key_codes+15*12+4],edx
      exit_ext0:
      no_ext_off:
    
        cmp  [ext2],0
        je   noext2dec
        dec  [ext2]
        jne  noext2dec
        mov    [key_codes+15*12+0],dword '----'
        mov    [key_codes+15*12+4],dword '----'
      noext2dec:
    
        mov  [ext],0
    
        cmp  eax,224
        jne  no_ext
        mov  [key_codes+15*12+0],dword '    '
        mov  [key_codes+15*12+4],dword '    '
        mov  [key_codes+15*12+8],dword 'Ext '
        mov  [ext],1
      no_ext:
    
        cmp  eax,225
        jne  no_ext2
        mov  [key_codes+15*12+0],dword '    '
        mov  [key_codes+15*12+4],dword '    '
        mov  [key_codes+15*12+8],dword 'Ext2'
        mov  [ext],2
        mov  [ext2],2
      no_ext2:
    
    
    сбивают с толку эти "роллинги" типа [key_codes+15*12+0]....
  • Мне кажется, приходят две клавиши по очереди: сначала 224/225, потом основной код. То-есть текущая клавиша может зависеть от предыдущей.
  • Pathoswithin wrote:Мне кажется, приходят две клавиши по очереди: сначала 224/225, потом основной код. То-есть текущая клавиша может зависеть от предыдущей.
    Нда! "Мне кажется" т.е. точно ты не знаешь, да ? Ладно буду рассматривать как-то более внимательно тот код.
    Я бы использовал ASC код, но там насколько я знаю нет понятия, что отжата клавиша или я ошибаюсь ? Т.е. имеется ввиду, что отжата какая-то конкретная клавиша, с конкретным кодом ? Вроде нету такого ?
  • Pathoswithin wrote:Мне кажется, приходят две клавиши по очереди: сначала 224/225, потом основной код.
    Да, нечто подобное происходит.

    ALEXS1983: вот пример, с одной стрелкой вверх.
    Attachments
    MoveRect2.png
    MoveRect2.png (4.35 KiB)
    Viewed 10461 times
    MoveRect2.7z (9.73 KiB)
    Downloaded 285 times
  • Who is online

    Users browsing this forum: No registered users and 0 guests