diff --git a/drivers/disk/Tupfile.lua b/drivers/disk/Tupfile.lua
index 8d3a88b15b..b1ce9d56b1 100644
--- a/drivers/disk/Tupfile.lua
+++ b/drivers/disk/Tupfile.lua
@@ -2,3 +2,4 @@ if tup.getconfig("NO_FASM") ~= "" then return end
ROOT = "../.."
tup.include(ROOT .. "/programs/use_fasm.lua")
tup.rule("tmpdisk.asm", "fasm %f %o " .. PESTRIP_CMD .. tup.getconfig("KPACK_CMD"), "%B.sys")
+tup.rule("virt_disk.asm", "fasm %f %o " .. PESTRIP_CMD .. tup.getconfig("KPACK_CMD"), "%B.sys")
\ No newline at end of file
diff --git a/drivers/disk/virt_disk.asm b/drivers/disk/virt_disk.asm
new file mode 100644
index 0000000000..9b1358b671
--- /dev/null
+++ b/drivers/disk/virt_disk.asm
@@ -0,0 +1,725 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; ;;
+;; Copyright (C) KolibriOS team 2023. All rights reserved. ;;
+;; Distributed under terms of the GNU General Public License ;;
+;; ;;
+;; Virtual disk driver for KolibriOS ;;
+;; ;;
+;; Written by Mikhail Frolov aka Doczom ;;
+;; ;;
+;; GNU GENERAL PUBLIC LICENSE ;;
+;; Version 2, June 1991 ;;
+;; ;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+DISK_STATUS_OK = 0 ; success
+DISK_STATUS_GENERAL_ERROR = -1; if no other code is suitable
+DISK_STATUS_INVALID_CALL = 1 ; invalid input parameters
+DISK_STATUS_NO_MEDIA = 2 ; no media present
+DISK_STATUS_END_OF_MEDIA = 3 ; end of media while reading/writing data
+
+; For all IOCTLs the driver returns one of the following error codes:
+NO_ERROR equ 0
+ERROR_INVALID_IOCTL equ 1 ; unknown IOCTL code
+
+maxPathLength = 1000h
+
+include '../struct.inc'
+
+; TODO list:
+; add support VDI image
+
+; Input structures:
+Flag:
+ .Ro = 1b
+ .Wo = 10b
+ .RW = 11b
+
+struct DISK_DEV
+ next rd 1
+ pref rd 1
+ SectorCount rd 2
+ DiskHandle rd 1
+ DiskNumber rd 1
+ Flags rd 1 ; 1-ro 2-wo 3-rw
+ TypeImage rd 1 ; 0-raw 1-vhd 2-vdi 3-imd
+ SectorSize rd 1
+ DiskPath rb maxPathLength
+ends
+struct IMAGE_ADD_STRUCT
+ Flags rd 1 ; 1-ro 2-wo 3-rw
+ TypeImage rd 1 ; 0-raw 1-vhd 2-vdi 3-imd
+ SectorSize rd 1
+ DiskPath rb maxPathLength
+ends
+
+struct DISKMEDIAINFO
+ Flags dd ?
+ SectorSize dd ?
+ Capacity dq ?
+ends
+
+ DEBUG = 1
+ __DEBUG__ = 1
+ __DEBUG_LEVEL__ = 1 ; 1 = verbose, 2 = errors only
+
+format PE DLL native 0.05
+entry START
+
+section '.flat' code readable writable executable
+
+include '../proc32.inc'
+include '../peimport.inc'
+include '../macros.inc'
+
+include '../fdo.inc'
+
+proc START c, state:dword, cmdline:dword
+ xor eax, eax ; set return value in case we will do nothing
+ cmp dword [state], 1
+ jne .nothing
+
+ mov ecx, disk_list_lock
+ invoke MutexInit
+
+ DEBUGF 1, "VIRT_DISK: driver loaded\n"
+ invoke RegService, my_service, service_proc
+ ret
+.nothing:
+ ret
+endp
+
+; get version
+; add disk
+; del disk
+; get list disks
+; get disk info
+proc service_proc
+ push ebx esi edi
+; 2. Get parameter from the stack: [esp+16] is the first parameter,
+; pointer to IOCTL structure.
+ mov ebx, [esp + 16] ; edx -> IOCTL
+
+ mov ecx, [ebx + IOCTL.io_code]
+ test ecx, ecx ; check for SRV_GETVERSION
+ jnz .add_disk
+
+ cmp [ebx + IOCTL.out_size], 4
+ jb .error_ret
+ mov eax, [ebx + IOCTL.output]
+ mov dword [eax], 1 ;API_VERSION
+ xor eax, eax
+ jmp .return
+
+.add_disk:
+ dec ecx ; check for DEV_ADD_DISK
+ jnz .del_disk
+
+ cmp [ebx + IOCTL.inp_size], sizeof.IMAGE_ADD_STRUCT
+ jb .error_ret
+
+ cmp [ebx + IOCTL.out_size], 4
+ jb .error_ret
+
+ invoke KernelAlloc, sizeof.DISK_DEV
+ test eax, eax
+ jz .error_ret
+
+ push eax
+ mov edi, eax
+ mov ecx, sizeof.DISK_DEV/4
+ xor eax, eax
+ rep stosd
+ pop eax
+
+ mov edi, eax
+ add edi, DISK_DEV.Flags
+ mov esi, [ebx + IOCTL.input]
+ mov ecx, sizeof.IMAGE_ADD_STRUCT/4
+ rep movsd
+
+ mov esi, eax ;save
+
+ cmp byte[esi + DISK_DEV.DiskPath], '/'
+ jnz .add_disk.error
+
+ mov ecx, disk_list_lock
+ invoke MutexLock
+
+ call get_free_num
+ jnz .add_disk.error2
+ mov [esi + DISK_DEV.DiskNumber], eax
+ mov ecx,[ebx + IOCTL.output]
+ mov [ecx], eax
+
+ ; init image
+ mov eax, [esi + DISK_DEV.TypeImage]
+ cmp eax, image_type.max_num
+ ja .add_disk.error2
+
+ call dword[image_type + eax*8] ;esi - DISK_DEV*
+ test eax, eax
+ jnz .add_disk.error2
+
+ ; creating name
+ push ebp
+ mov ebp, esp
+
+ mov eax, [esi + DISK_DEV.DiskNumber]
+ mov edi, esp
+ dec edi
+ mov byte[edi], 0
+ mov ecx, 10
+@@:
+ xor edx, edx
+ div ecx
+ add edx,'0'
+ dec edi
+ mov byte[edi], dl
+ test eax, eax
+ jnz @b
+
+ mov esp, edi
+ push word 'vd'
+ sub edi, 2
+
+ ; add disk
+ mov eax, [esi + DISK_DEV.TypeImage]
+ invoke DiskAdd, dword[image_type + eax*8 + 4] , \
+ edi, esi, 0
+ mov [esi + DISK_DEV.DiskHandle], eax
+ mov esp, ebp
+ pop ebp
+ test eax, eax
+ jz .add_disk.error2
+
+ invoke DiskMediaChanged, eax, 1
+
+ ; add in list
+ mov dword[esi], disk_root_list
+ mov eax, [disk_root_list + 4]
+ mov [esi + 4], eax
+ mov [eax], esi
+ mov [disk_root_list + 4], esi
+ inc dword[disk_count]
+
+ mov ecx, disk_list_lock
+ invoke MutexUnlock
+
+ xor eax, eax
+ jmp .return
+
+.add_disk.error2:
+ mov ecx, disk_list_lock
+ invoke MutexUnlock
+
+.add_disk.error:
+ invoke KernelFree, esi
+ jmp .error_ret
+
+
+.del_disk:
+ dec ecx ; check for DEV_DEL_DISK
+ jnz .get_root_list
+
+ cmp [ebx + IOCTL.inp_size], 4
+ jb .error_ret
+
+ mov ecx, [ebx + IOCTL.input]
+ mov ecx, [ecx]
+ call get_disk
+
+ test eax, eax
+ jz .error_ret
+
+ cmp [eax + DISK_DEV.DiskHandle], 0
+ jz .error_ret
+ mov ecx, [eax + DISK_DEV.DiskHandle]
+ mov [eax + DISK_DEV.DiskHandle], 0
+ invoke DiskDel, ecx
+
+ xor eax, eax
+ jmp .return
+
+.get_root_list:
+ dec ecx ; check for DEV_DEL_DISK
+ jnz .get_disk_info
+
+ cmp [ebx + IOCTL.inp_size], 4*2 ; offset + count
+ jb .error_ret
+ mov ecx, [ebx + IOCTL.input]
+ mov edx, [ecx] ; offset
+ mov eax, [ecx + 4] ; count
+
+ add edx, eax
+ cmp edx, [disk_count]
+ ja .error_ret
+
+ xor edx, edx
+ imul eax, sizeof.DISK_DEV - 8
+ add eax, 4
+ cmp [ebx + IOCTL.out_size], eax
+ jb .error_ret
+
+ mov edi, [ebx + IOCTL.output]
+ mov eax, [disk_count]
+ stosd
+
+ mov edx, [ecx]
+ mov eax, [disk_root_list]
+@@:
+ test edx, edx
+ jz @f
+ mov eax, [eax]
+ dec edx
+ jmp @b
+@@:
+ mov edx, [ecx + 4]
+@@:
+ test edx, edx
+ jz @f
+ mov esi, eax
+ add esi, 8
+ mov ecx, (sizeof.DISK_DEV - 8)/4
+ rep movsd
+ mov eax, [eax]
+ dec edx
+ jmp @b
+@@:
+ xor eax, eax
+ jmp .return
+
+.get_disk_info:
+ dec ecx
+ jnz .error_ret
+
+ cmp [ebx + IOCTL.inp_size], 4
+ jb .error_ret
+ cmp [ebx + IOCTL.out_size], sizeof.DISK_DEV - 8
+ jb .error_ret
+ mov ecx, [ebx + IOCTL.input]
+ mov ecx, [ecx]
+
+ call get_disk
+ test eax, eax
+ jz .error_ret
+
+ mov esi, eax
+ add esi, 4*2
+ mov edi, [ebx + IOCTL.output]
+ mov ecx, (sizeof.DISK_DEV - 8)/4
+ rep movsd
+
+ xor eax, eax
+ jmp .return
+
+.error_ret:
+ mov eax, ERROR_INVALID_IOCTL
+.return:
+ pop edi esi ebx
+ retn 4
+endp
+
+; IN: ecx - ptr DISK_DEV
+; OUT: ZF - found zF - not found
+proc disk_dev_check
+ push eax
+ mov eax, disk_root_list
+@@:
+ mov eax, [eax]
+ cmp eax, disk_root_list
+ jz .nf
+ cmp eax, ecx
+ jnz @b
+ pop eax
+ ret
+.nf:
+ test eax, eax
+ pop eax
+ ret
+endp
+; IN: ecx - disk number
+; OUT: eax - ptr DISK_DEV
+proc get_disk
+ push ecx
+ mov ecx, disk_list_lock
+ invoke MutexLock
+ pop ecx
+
+ mov eax, disk_root_list
+@@:
+ mov eax, [eax]
+ cmp eax, disk_root_list
+ jz .nf
+ cmp ecx, [eax + DISK_DEV.DiskNumber]
+ jnz @b
+
+ push eax
+ mov ecx, disk_list_lock
+ invoke MutexUnlock
+ pop eax
+ ret
+.nf:
+ mov ecx, disk_list_lock
+ invoke MutexUnlock
+
+ xor eax, eax
+ ret
+endp
+
+; OUT: eax - number free disk
+; Zf - good zf - not found
+proc get_free_num
+ xor eax, eax
+ mov edx, disk_root_list
+@@:
+ mov edx, [edx]
+ cmp edx, disk_root_list
+ jz @f
+ cmp eax, [edx + DISK_DEV.DiskNumber]
+ jnz @b
+
+ inc eax
+ jnz @b
+
+ test edx, edx ; random :)
+@@:
+ ret
+endp
+
+;; IN: ecx - number disk
+;; OUT: eax - ptr to DISK_DEV or zero
+;proc get_link_disk
+;
+; push ecx
+; mov ecx, disk_list_lock
+; invoke MutexLock
+; pop ecx
+;
+; xor eax, eax
+; cmp ecx, [disk_array.len]
+; jae .end
+;
+; mov eax, [disk_array]
+; mov eax, [edx + ecx*4]
+;
+;.end:
+; push eax
+; ; unlock disk list
+; mov ecx, disk_list_lock
+; invoke MutexUnlock
+; pop eax
+; ret
+;endp
+;; IN: esi - ptr to DISK_DEV
+;; OUT: eax - offset in array or -1
+;proc add_link_disk
+; ; find free item
+; mov ecx, [disk_array]
+; xor eax, eax
+; dec eax
+;@@:
+; inc eax
+; cmp eax, [disk_array.len]
+; jae .not_found
+;
+; cmp dword[ecx + eax*4], 0
+; jnz @b
+;
+; mov [ecx + eax*4], esi
+; ret
+;
+;.not_found:
+; inc dword[disk_array.len]
+; ;get new memory
+; mov eax,[disk_array.len]
+; shl eax, 2 ;*4
+; invoke Kmalloc
+; test eax, eax
+; jz .err
+; ; copy data
+; push edi esi
+; mov ecx, [disk_array.len]
+; mov edi, eax
+; mov esi, [disk_array]
+; rep movsd
+; pop esi edi
+; ; del old array
+; xchg [disk_array], eax
+; invoke Kfree
+; mov eax, [disk_array.len]
+; ret
+;.err:
+; dec dword[disk_array.len]
+; mov eax, -1
+; ret
+;endp
+;
+;; IN: ecx - offset in array
+;proc del_link_disk
+; mov edx, ecx
+; dec edx
+; cmp edx,[disk_array.len]
+; jz .last_item
+;
+; mov edx,[disk_array]
+; mov [edx + ecx*4], 0
+; ret
+;.last_item:
+; dec dword[disk_array.len]
+; ;get new memory
+; mov eax,[disk_array.len]
+; shl eax, 2 ;*4
+; invoke Kmalloc
+; test eax, eax
+; jz .err
+; ; copy data
+; push edi esi
+; mov ecx, [disk_array.len]
+; mov edi, eax
+; mov esi, [disk_array]
+; rep movsd
+; pop esi edi
+; ; del old array
+; xchg [disk_array], eax
+; invoke Kfree
+; ret
+;.err:
+; inc dword[disk_array.len]
+; mov eax, -1
+; ret
+;endp
+
+; RAW IMAGE DISK FUNCTIONS
+proc raw_disk_close stdcall, pdata
+ ; del item list
+ mov ecx, disk_list_lock
+ invoke MutexLock
+
+ mov ecx, [pdata]
+ mov eax, [ecx] ; eax = next
+ mov edx, [ecx + 4] ; edx = prev
+ mov [eax + 4], edx ; [next.prev] = prev
+ mov [edx], eax ; [prev.next] = next
+
+ dec dword[disk_count]
+
+ mov ecx, disk_list_lock
+ invoke MutexUnlock
+
+ invoke KernelFree, [pdata]
+ DEBUGF 1, "VIRT_DISK: disk deleted\n"
+ ret
+endp
+
+proc disk_querymedia stdcall, pdata, mediainfo
+ mov eax, [mediainfo]
+ mov edx, [pdata]
+ mov [eax + DISKMEDIAINFO.Flags], 0
+ mov ecx, [edx + DISK_DEV.SectorSize]
+ mov [eax + DISKMEDIAINFO.SectorSize], ecx
+ mov ecx, [edx + DISK_DEV.SectorCount]
+ mov dword[eax + DISKMEDIAINFO.Capacity], ecx
+ mov ecx, [edx + DISK_DEV.SectorCount + 4]
+ mov dword[eax + DISKMEDIAINFO.Capacity + 4], ecx
+ xor eax, eax
+ ret
+endp
+
+proc raw_disk_rd stdcall pdata: dword,\
+ buffer: dword,\
+ startsector: qword,\
+ numsectors_ptr:dword
+
+ mov ecx, [pdata]
+ test [ecx + DISK_DEV.Flags], Flag.Ro
+ jz .no_support
+
+ pusha
+ lea eax,[ecx + DISK_DEV.DiskPath]
+ push eax
+ dec esp
+ mov byte[esp], 0
+ push dword[buffer]
+
+ mov eax, [numsectors_ptr]
+ mov eax, [eax]
+ mul dword[ecx + DISK_DEV.SectorSize]
+ push eax
+ ; get offset for startsector
+ mov eax, dword[startsector]
+ xor edx, edx
+ mul dword[ecx + DISK_DEV.SectorSize]
+ push edx
+ push eax
+ mov eax, dword[startsector + 4]
+ mul dword[ecx + DISK_DEV.SectorSize]
+ add [esp + 4], eax
+ push dword 0 ;read file
+
+ mov ebx, esp
+ invoke FS_Service
+ push eax
+ mov ecx, [pdata]
+ mov eax, ebx
+ xor edx, edx
+ div dword[ecx + DISK_DEV.SectorSize]
+ mov edx, [numsectors_ptr]
+ mov [edx], eax
+ pop eax
+
+ add esp, 6*4+1 ; size FS struct
+ test eax, eax
+ popa
+ jz @f
+ mov eax, 1
+ ret
+@@:
+ xor eax, eax
+ ret
+.no_support:
+ mov eax, DISK_STATUS_GENERAL_ERROR
+ ret
+endp
+
+
+proc raw_disk_wr stdcall pdata: dword,\
+ buffer: dword,\
+ startsector: qword,\
+ numsectors_ptr:dword
+
+ mov ecx, [pdata]
+ test [ecx + DISK_DEV.Flags], Flag.Wo
+ jz .no_support
+
+ pusha
+ lea eax,[ecx + DISK_DEV.DiskPath]
+ push eax
+ dec esp
+ mov byte[esp],0
+ push dword[buffer]
+
+ mov eax, [numsectors_ptr]
+ mov eax, [eax]
+ mul dword[ecx + DISK_DEV.SectorSize]
+ push eax
+ ; get offset for startsector
+ mov eax, dword[startsector]
+ xor edx, edx
+ mul dword[ecx + DISK_DEV.SectorSize]
+ push edx
+ push eax
+ xor edx, edx
+ mov eax, dword[startsector + 4]
+ mul dword[ecx + DISK_DEV.SectorSize]
+ add [esp + 4], eax
+ push dword 3 ; write file
+ mov ebx, esp
+ invoke FS_Service
+
+ push eax
+ mov ecx, [pdata]
+ mov eax, ebx
+ xor edx, edx
+ div dword[ecx + DISK_DEV.SectorSize]
+ mov edx, [numsectors_ptr]
+ mov [edx], eax
+ pop eax
+
+ add esp, 6*4+1 ; size FS struct
+ test eax, eax
+ popa
+ jz @f
+ mov eax, 1
+ ret
+@@:
+ xor eax, eax
+ ret
+
+.no_support:
+ mov eax, DISK_STATUS_GENERAL_ERROR
+ ret
+endp
+
+disk_root_list:
+ dd disk_root_list
+ dd disk_root_list
+disk_count: dd 0
+disk_list_lock: MUTEX
+
+image_type:
+ ; init function, table disk function
+ dd raw_image_init, raw_disk_functions
+ ; vdi
+.max_num = ($ - image_type - 8) / 8; 8 - item size
+
+align 4
+; esi - ptr to DISK_DEV
+;WARNING: raw image size >=2tb not supported.
+
+proc raw_image_init
+ sub esp, 40 ; for file_info
+ mov ecx, esp
+
+ pusha
+ lea eax,[esi + DISK_DEV.DiskPath]
+ push eax
+ dec esp
+ mov byte[esp],0
+ push ecx
+ xor eax, eax
+ push eax eax eax
+ push dword 5
+
+ mov ebx, esp
+ invoke FS_Service
+ add esp, 6*4+1
+ test eax, eax
+ popa
+ lea esp,[esp + 40]
+ jnz .err
+
+ ; WARNING: Not working with stack, destroys the structure!
+ mov eax, [ecx + 32]
+ mov edx, [ecx + 36]
+ test eax, eax
+ jz .err
+
+ div dword[esi + DISK_DEV.SectorSize] ; undefined exeption
+ mov [esi + DISK_DEV.SectorCount], eax
+ mov [esi + DISK_DEV.SectorCount + 4], 0
+ ; END WARNING
+
+ xor eax, eax
+ ret
+.err:
+ or eax, -1
+ ret
+endp
+
+align 4
+raw_disk_functions:
+ dd .size
+ dd raw_disk_close
+ dd 0 ; no need in .closemedia
+ dd disk_querymedia
+ dd raw_disk_rd
+ dd raw_disk_wr
+ dd 0 ; no need in .flush
+ dd disk_adjust_cache_size
+.size = $ - raw_disk_functions
+
+proc disk_adjust_cache_size
+ virtual at esp+4
+ .userdata dd ?
+ .suggested_size dd ?
+ end virtual
+ xor eax, eax
+ retn 8
+endp
+
+my_service db 'VIRT_DISK',0
+
+data fixups
+end data
+
+include_debug_strings
\ No newline at end of file
diff --git a/programs/system/virtdisk/README.md b/programs/system/virtdisk/README.md
new file mode 100644
index 0000000000..bd763bdf69
--- /dev/null
+++ b/programs/system/virtdisk/README.md
@@ -0,0 +1,34 @@
+# VIRT_DISK
+Driver for mounting RAW disk images in KolibriOS.
+
+To demonstrate the operation of the driver, the virtdisk program was written. Program allows you to add, delete and view virtual disks.
+![foto](https://github.com/Doczom/VIRT_DISK/blob/main/utils/scr_1.png)
+
+## List of virtdisk arguments:
+ - Delete command:
+
+ virtdisk -d
+
+ - Information from disk:
+
+ virtdisk -i
+
+ - Add disk image in file system:
+
+ virtdisk -a -s -t -f
+
+ - Input list all virtual disks:
+
+ virtdisk -l
+
+## List flags:
+ - ro
- read only access
+ - rw
- read-write access
+
+## List disk image types:
+ - RAW
- it is used to mount disk images in "raw", "img" and "iso" formats
+
+## Exemples command:
+ virtdisk -a /sd0/4/kolibri.img -f ro
+
+ virtdisk -d 3
diff --git a/programs/system/virtdisk/Tupfile.lua b/programs/system/virtdisk/Tupfile.lua
new file mode 100644
index 0000000000..2ffe0e070f
--- /dev/null
+++ b/programs/system/virtdisk/Tupfile.lua
@@ -0,0 +1,2 @@
+if tup.getconfig("NO_FASM") ~= "" then return end
+tup.rule("virtdisk.asm", "fasm %f %o " .. tup.getconfig("KPACK_CMD"), "virtdisk")
diff --git a/programs/system/virtdisk/parser.inc b/programs/system/virtdisk/parser.inc
new file mode 100644
index 0000000000..00bec8c86f
--- /dev/null
+++ b/programs/system/virtdisk/parser.inc
@@ -0,0 +1,322 @@
+; data for parsing string
+param_cmd: dd 0 ;set when for "-a" command
+
+; virtdisk -d
+; virtdisk -i
+; virtdisk -a -s -t -f
+; virtdisk -l
+parse_cmd:
+ mov edi, PATH
+ ; find string length
+ xor al, al
+ mov ecx, 4096
+ repne scasb
+
+ mov ecx, edi
+ sub ecx, PATH
+ mov edi, PATH
+.still:
+ mov al, ' '
+ repz scasb
+
+ test ecx, ecx
+ jz .end_parser
+
+ dec edi
+ or word[edi], 0x2020 ; яхЁхтюфшь т эшцэшщ ЁхушёЄЁ
+ ; -a -d -i -l -s -t -f
+
+ cmp word[edi], '-a'
+ jnz @f
+ ;add virt disk
+ mov dword[param_cmd],-1
+
+ add edi, 3
+ sub ecx, 2
+ js ERROR_EXIT ; error not found path
+
+ mov edx, add_disk.file
+ call .copy_str
+ or dword[edx -4], 0x20202020
+ mov dword[add_disk.size], 512
+ cmp dword[edx -4], '.iso'
+ jnz .still
+ mov dword[add_disk.size], 2048
+
+ jmp .still
+@@:
+ cmp word[edi], '-d'
+ jnz @f
+
+ add edi, 3
+ sub ecx, 2
+ js ERROR_EXIT ; error not found path
+
+ call .get_number
+ mov [disk_num],eax
+
+ pusha
+ mov al, 68
+ mov bl, 17
+ mov ecx, ioctl_del_disk
+ int 0x40
+
+ push str_command_successfully
+ call _sc_puts
+ popa
+
+ jmp .still
+@@:
+ cmp word[edi], '-i'
+ jnz .no_disk_info
+ ; write info
+ add edi, 3
+ sub ecx, 2
+ js ERROR_EXIT ; error not found path
+ ; get disk number
+ call .get_number
+ mov [disk_num],eax
+
+ pusha
+ mov al, 68
+ mov bl, 17
+ mov ecx, ioctl_info_disk
+ int 0x40
+
+ call write_disk_info
+ popa
+ jmp .still
+
+.no_disk_info:
+ cmp word[edi], '-l'
+ jnz .no_disk_list
+ ; write list disks
+ add edi, 2
+ sub ecx, 1
+ pusha
+
+ mov al, 68
+ mov bl, 17
+ mov ecx, ioctl_count_disk
+ int 0x40
+ test eax, eax
+ jnz ERROR_EXIT
+
+ push str_header_disk_list
+ call _sc_puts
+
+ mov ecx, ioctl_list_disk.count
+ mov eax, 68
+ mov bl, 12
+ imul ecx, sizeof.info_buffer
+ add ecx, 4
+ mov [ioctl_list_disk.size_buffer], ecx
+ int 0x40
+ test eax, eax
+ jz ERROR_EXIT
+
+ mov [ioctl_list_disk.buffer], eax
+ mov esi, eax
+ mov edi, eax
+ add esi, 4
+
+ mov al, 68
+ mov bl, 17
+ mov ecx, ioctl_list_disk
+ int 0x40
+ test eax, eax
+ jnz ERROR_EXIT
+
+ cmp dword[edi], 0
+ jz .end_list
+.next_item_list:
+
+ ; num2str
+ push dword 10
+ mov ecx, esp
+ mov eax, [esi + info_buffer.disk_num - info_buffer]
+@@:
+ xor edx, edx
+ div dword[esp]
+ dec ecx
+ add dl, '0'
+ mov byte[ecx], dl
+ test eax, eax
+ jnz @b
+
+ mov edx, str_input_disk_number + 1
+ mov dword[edx], ' '
+@@:
+ mov al, byte[ecx]
+ mov byte[edx], al
+ inc edx
+ inc ecx
+ cmp ecx, esp
+ jnz @b
+ ;-------
+ mov ecx, esp
+ mov eax, [esi + info_buffer.sector_size - info_buffer]
+@@:
+ xor edx, edx
+ div dword[esp]
+ dec ecx
+ add dl, '0'
+ mov byte[ecx], dl
+ test eax, eax
+ jnz @b
+
+ mov edx, str_input_disk_sector
+ mov dword[edx], ' '
+@@:
+ mov al, byte[ecx]
+ mov byte[edx], al
+ inc edx
+ inc ecx
+ cmp ecx, esp
+ jnz @b
+ ;-------
+ add esp, 4
+ ; flags
+ mov dword[str_input_disk_flags], ' '
+ cmp dword[esi + info_buffer.flags - info_buffer], 1b
+ jnz @f
+ mov word[str_input_disk_flags], 'ro'
+@@:
+ cmp dword[esi + info_buffer.flags - info_buffer], 11b
+ jnz @f
+ mov word[str_input_disk_flags], 'rw'
+@@:
+ ;-------
+ pusha
+ add esi, info_buffer.path - info_buffer
+ push esi
+ push str_input_disk_number
+ call _sc_puts
+ call _sc_puts
+ push str_newline
+ call _sc_puts
+ popa
+
+ add esi, sizeof.info_buffer
+ dec dword[edi]
+ jnz .next_item_list
+.end_list:
+
+ mov eax, 68
+ mov ebx, 13
+ mov ecx, edi
+ int 0x40
+
+ popa
+ jmp .still
+.no_disk_list:
+ cmp dword[param_cmd],0
+ jz .no_cmd
+
+ cmp word[edi], '-s'
+ jnz .no_sector_size
+ ; set sector size for -a command
+ add edi, 3
+ sub ecx, 2
+ js ERROR_EXIT ; error
+ ; get number
+ call .get_number
+ mov [add_disk.size], eax
+ jmp .still
+.no_sector_size:
+ cmp word[edi], '-t'
+ jnz .no_disk_type
+ ; set image type for -a command
+ add edi, 3+3
+ sub ecx, 2+3
+ js ERROR_EXIT ; error
+
+ or dword[edi - 4], 0x20202020
+ cmp dword[edi - 4], ' raw'
+ jnz .still
+ ; TODO!!!
+ mov dword[add_disk.type], 0
+ jmp .still
+.no_disk_type:
+ cmp word[edi], '-f'
+ jnz .no_cmd
+ ; set flags for -a command
+ add edi, 3+2
+ sub ecx, 2+2
+ js ERROR_EXIT ; error
+
+ or word[edi - 2], 0x2020
+ cmp word[edi - 2], 'ro'
+ jnz @f
+ mov dword[add_disk.flags], 1b
+
+@@: cmp word[edi - 2], 'rw'
+ jnz .still
+ mov dword[add_disk.flags], 11b
+ jmp .still
+.no_cmd:
+ inc edi
+ jmp .still
+.end_parser:
+ ret
+
+.get_str:
+ push edi
+ inc dword[esp]
+ mov al, '"'
+ cmp byte[edi], al
+ jz @f
+ dec dword[esp]
+ mov al, ' '
+ dec edi
+@@:
+ inc edi
+ repne scasb
+ and byte[edi - 1], 0
+ pop eax
+ ret
+
+; edx - buffer
+.copy_str:
+ mov al, ' '
+ cmp byte[edi], '"'
+ jnz @f
+ mov al, '"'
+ inc edi
+ dec ecx
+@@:
+ mov ah, byte[edi]
+ test ah, ah
+ jz @f
+ cmp ah, al
+ jz @f
+ mov byte[edx], ah
+ inc edx
+ inc edi
+ dec ecx
+ jmp @b
+@@:
+ mov byte[edx], 0
+ ret
+
+.get_number:
+ xor eax, eax
+@@:
+ movzx edx, byte[edi]
+ test edx, edx
+ jz @f
+ cmp dl, ' '
+ jz @f
+ sub dl, '0'
+ js ERROR_EXIT
+
+ cmp dl, 9
+ ja ERROR_EXIT
+
+ imul eax, 10
+ add eax, edx
+ dec ecx
+ inc edi
+ jmp @b
+@@:
+ ret
\ No newline at end of file
diff --git a/programs/system/virtdisk/shell.inc b/programs/system/virtdisk/shell.inc
new file mode 100644
index 0000000000..6f2fa1eaff
--- /dev/null
+++ b/programs/system/virtdisk/shell.inc
@@ -0,0 +1,300 @@
+
+
+SC_OK = 0
+SC_EXIT = 1
+SC_PUTC = 2
+SC_PUTS = 3
+SC_GETC = 4
+SC_GETS = 5
+SC_CLS = 6
+SC_PID = 7
+SC_PING = 8
+
+SHM_WRITE = 0x01
+SHM_OPEN_ALWAYS = 0x04
+
+;============================
+
+align 4
+sc_name rb 64
+sc_pid dd 0
+sc_buffer dd 0
+sc_process dd 0
+
+;============================
+
+if used _sc_pid2name
+align 4
+_sc_pid2name:
+
+ push esp
+ push ebx
+
+ xor ecx, ecx
+ mov eax, [sc_pid]
+ mov ebx, 10
+@@:
+ xor edx, edx
+ div ebx
+ push edx
+ inc ecx
+ test eax, eax
+ jnz @b
+
+mov edi, sc_name
+
+@@:
+ pop eax
+ add al, '0'
+ stosb
+ loop @b
+
+
+ mov al, '-'
+ stosb
+ mov al, 'S'
+ stosb
+ mov al, 'H'
+ stosb
+ mov al, 'E'
+ stosb
+ mov al, 'L'
+ stosb
+ mov al, 'L'
+ stosb
+ mov al, 0
+ stosb
+
+
+ pop ebx
+ pop esp
+
+ ret
+end if
+
+;============================
+
+if used _sc_init
+align 4
+; void __stdcall sc_init();
+_sc_init:
+
+ push esp
+ push ebx
+
+ mov eax, 68
+ mov ebx, 11
+ int 0x40
+
+ mov eax, 68 ; выделить память
+ mov ebx, 12
+ mov ecx, 1024
+ int 0x40
+
+ mov [sc_process], eax
+
+ mov eax, 9 ; получить информацию о текущем процессе
+ mov ebx, [sc_process]
+ mov ecx, -1
+ int 0x40
+
+ mov dword eax, [ebx+30] ; получаем PID текщего процесса
+ mov [sc_pid], eax
+
+ mov eax, 68 ; освободить память
+ mov ebx, 13
+ mov ecx, [sc_process]
+ int 0x40
+
+ call _sc_pid2name
+
+ mov eax, 68 ; открыть именованную область
+ mov ebx, 22
+ mov dword ecx, sc_name
+ mov edx, 4096
+ mov esi, SHM_OPEN_ALWAYS or SHM_WRITE
+ int 0x40
+
+ mov [sc_buffer], eax
+
+ pop ebx
+ pop esp
+
+ ret
+end if
+
+;============================
+
+if used _sc_puts
+align 4
+; void __stdcall sc_puts(char *str);
+_sc_puts:
+
+ push esp
+ push ebx
+
+ mov esi, [esp+12]
+ mov edi, [sc_buffer]
+ mov al, SC_PUTS
+ stosb
+
+@@:
+ lodsb
+ stosb
+ test al, al
+ jnz @b
+
+ mov ebx, [sc_buffer]
+
+@@:
+ mov byte dl, [ebx]
+ test dl, dl
+ jz @f
+ push ebx
+ mov eax, 5
+ mov ebx, 5
+ int 0x40
+ pop ebx
+ jmp @b
+
+@@:
+
+ pop ebx
+ pop esp
+ ret 4
+end if
+
+;============================
+
+if used _sc_exit
+align 4
+; void __stdcall sc_exit();
+_sc_exit:
+ push ebx
+ push esp
+
+ mov ebx, [sc_buffer]
+ mov byte [ebx], SC_EXIT
+
+@@:
+ mov byte dl, [ebx]
+ test dl, dl
+ jz @f
+ push ebx
+ mov eax, 5
+ mov ebx, 5
+ int 0x40
+ pop ebx
+ jmp @b
+
+@@:
+ mov eax, 68 ;закрыть именованную область
+ mov ebx, 23
+ mov dword ecx, sc_name
+ int 0x40
+
+ pop esp
+ pop ebx
+ ret
+end if
+
+
+;============================
+
+if used _sc_gets
+align 4
+; void __stdcall sc_gets(char *str);
+_sc_gets:
+
+ push esp
+ push ebx
+
+ mov edi, [esp+12]
+
+ mov ebx, [sc_buffer]
+ mov byte [ebx], SC_GETS
+
+@@:
+ mov byte dl, [ebx]
+ test dl, dl
+ jz @f
+ push ebx
+ mov eax, 5
+ mov ebx, 5
+ int 0x40
+ pop ebx
+ jmp @b
+
+@@:
+
+
+ mov esi, [sc_buffer]
+ inc esi
+
+@@:
+ lodsb
+ stosb
+ test al, al
+ jnz @b
+
+ pop ebx
+ pop esp
+ ret 4
+end if
+
+;============================
+
+if used _sc_pid
+_sc_pid:
+;int __stdcall sc_pid (void);
+ push ebx ecx
+
+ mov ecx, [sc_buffer]
+ mov byte [ecx], SC_PID
+
+@@:
+ mov eax, 5
+ mov ebx, 5
+ int 0x40
+
+ cmp byte [ecx], 0
+ je @f
+ call _sc_ping
+ test eax, eax
+ jnz .err
+
+@@:
+ mov eax, [ecx+1]
+ pop ecx ebx
+ ret
+
+.err:
+ pop ecx ebx
+ xor eax, eax
+ dec eax
+ ret
+end if
+
+;============================
+
+if used _sc_ping
+_sc_ping:
+;int __stdcall sc_ping (void);
+ push ebx ecx
+
+ mov ecx, [sc_buffer]
+ mov byte [ecx], SC_PING
+
+ mov eax, 5
+ mov ebx, 200
+ int 0x40
+
+ xor eax, eax
+ cmp byte [ecx], 0
+ je @f
+ dec eax
+
+@@:
+ pop ecx ebx
+ ret
+end if
diff --git a/programs/system/virtdisk/virtdisk.asm b/programs/system/virtdisk/virtdisk.asm
new file mode 100644
index 0000000000..cd983cde99
--- /dev/null
+++ b/programs/system/virtdisk/virtdisk.asm
@@ -0,0 +1,173 @@
+;-----------------------------------------------------------------------------;
+; Copyright (C) 2023, Mikhail Frolov aka Doczom . All rights reserved. ;
+; Distributed under terms of the GNU General Public License ;
+; ;
+; Demo program for the VIRT_DISK driver. ;
+; ;
+; GNU GENERAL PUBLIC LICENSE ;
+; Version 2, June 1991 ;
+; ;
+;-----------------------------------------------------------------------------;
+format binary as ""
+ use32
+ org 0
+ db 'MENUET01'
+ dd 1, START, I_END, MEM, STACKTOP, PATH, 0
+
+include 'parser.inc'
+include 'shell.inc'
+START:
+ call _sc_init
+
+ mov al, 68
+ mov bl, 16
+ mov ecx, drv_name
+ int 0x40
+ mov [ioctl_add_disk.hand], eax
+ mov [ioctl_del_disk.hand], eax
+ mov [ioctl_info_disk.hand], eax
+ mov [ioctl_list_disk.hand], eax
+ mov [ioctl_count_disk.hand], eax
+ test eax, eax
+ jz .end
+
+ cmp byte[PATH], 0
+ jz .end
+
+ call parse_cmd
+
+ cmp dword[param_cmd],0
+ jz .end
+
+ mov al, 68
+ mov bl, 17
+ mov ecx, ioctl_add_disk
+ int 0x40
+ test eax, eax
+ jnz @f
+ push str_command_successfully
+ call _sc_puts
+ jmp .end
+@@:
+ push str_error
+ call _sc_puts
+.end:
+ call _sc_exit
+ mov eax,-1
+ int 0x40
+ERROR_EXIT:
+ push str_runtime_err
+ call _sc_puts
+
+ call _sc_exit
+ mov eax,-1
+ int 0x40
+write_disk_info:
+ pusha
+ push str_disk_info.path
+ call _sc_puts
+
+ push info_buffer.path
+ call _sc_puts
+
+ push str_newline
+ call _sc_puts
+ popa
+ ret
+
+
+I_END:
+drv_name: db 'VIRT_DISK',0
+
+; messages
+str_runtime_err:
+ db 'Runtime error', 13, 10, 0
+str_command_successfully:
+ db 'Command successfully', 13, 10, 0
+str_header_disk_list:
+ db ' disk | sector | flags | file', 13, 10
+ db '-------|--------|-------|---------------------',13, 10, 0
+str_input_disk_number:
+ db ' | ' ; ,0
+str_input_disk_sector:
+ db ' | ';,0
+str_input_disk_flags:
+ db ' | ',0
+str_error:
+ db 'Error',0
+str_disk_info:
+.num: db 'Disk number: ',0
+;.type: db 'Type: ', 0
+;.sector_size:
+; db 'Sector size: ', 0
+.path: db 'File: ', 0
+str_newline:
+ db ' ', 13, 10, 0
+
+ioctl_count_disk:
+.hand: dd 0, 3 ;iocode
+ dd .get_count_disk, 8
+ dd ioctl_list_disk.count, 4
+.get_count_disk:
+ dd 0, 0
+
+ioctl_list_disk:
+.hand: dd 0, 3 ;iocode
+ dd .inp, 8
+.buffer:
+ dd 0
+.size_buffer:
+ dd 0
+.inp:
+ dd 0
+.count: dd 0
+
+ioctl_info_disk:
+.hand: dd 0, 4 ;iocode
+ dd disk_num, 4
+ dd info_buffer, sizeof.info_buffer
+ioctl_del_disk:
+.hand: dd 0, 2 ;iocode
+ dd disk_num, 4
+ dd 0, 0
+ioctl_add_disk:
+.hand: dd 0, 1 ;iocode
+ dd add_disk, add_disk.end - add_disk
+ dd disk_num, 4
+
+disk_num: rd 0
+add_disk:
+.flags: dd 11b ;rw
+.type: dd 0 ; TypeImage 0 - RAW
+.size: dd 512
+.file: rb 4096
+.end:
+
+PATH: rb 4096
+
+info_buffer:
+.sector_count: rd 2
+.disk_hand: rd 1
+.disk_num: rd 1
+.flags: rd 1
+.type: rd 1
+.sector_size: rd 1
+.path: rb 4096
+sizeof.info_buffer = $ - info_buffer
+
+ rb 4096
+STACKTOP:
+MEM:
+; EXAMPLE COMMANDS:
+; virtdisk -f/sd0/4/kolibri.img -s512
+; virtdisk -f/sd0/4/kolibri.img
+; virtdisk -f/sd0/4/kolibri.iso -s2048
+; default sector size = 512 for all disk
+; 2048 for ISO disk
+
+;struct IMAGE_ADD_STRUCT
+; Flags rd 1 ; 1-ro 2-wo 3-rw
+; TypeImage rd 1 ; 0-raw 1-vhd 2-vdi 3-imd
+; SectorSize rd 1
+; DiskPath rb maxPathLength
+;ends
\ No newline at end of file