pascalx.osapi.pas

Переключить прокрутку окна
Загрузить этот исходный код

{
    pascalx.osapi — модуль для взаимодействия с операционной системой.

    Copyright © 2021 Малик Разработчик

    Это свободная программа: вы можете перераспространять её и/или
    изменять её на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Эта программа распространяется в надежде, что она может быть полезна,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЁННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <http://www.gnu.org/licenses/>.
}

unit pascalx.osapi;

{$MODE DELPHI}

interface

uses
    windows,
    pascalx.lang,
    pascalx.utils,
    pascalx.io,
    pascalx.io.vfs;

{$ASMMODE INTEL,CALLING REGISTER,TYPEINFO ON}

{%region public }
const { значения для MemoryRegion }
    READABLE = int($01);
    WRITEABLE = int($02);
    EXECUTABLE = int($04);

type
    MemoryRegion = class;
    LocalFileSystem = class;
    HandleInputStream = class;
    HandleOutputStream = class;
    FileInputStream = class;
    FileOutputStream = class;
    FileIOStream = class;

    MemoryRegion = class(DynamicalyAllocatedObject)
    public
        class function getMinimumAddress(): long; static;
        class function getMaximumAddress(): long; static;
        class function allocate(address: long; size: long; protectionFlags: int): Pointer; static; overload;
        class function allocate(address: Pointer; size: long; protectionFlags: int): Pointer; static; overload;
        class procedure deallocate(address: Pointer; size: long); static; overload;
        class procedure protect(address: long; size: long; protectionFlags: int); static; overload;
        class procedure protect(address: Pointer; size: long; protectionFlags: int); static; overload;
        class procedure getInfo(address: long; region: MemoryRegion); static; overload;
        class procedure getInfo(address: Pointer; region: MemoryRegion); static; overload;
    private
        fProtectionFlags: int;
        fAddress: long;
        fSize: long;
        fModule: UnicodeString;
        function isModuleStored(): boolean;
        function getProtectionFlag(flagMask: int): boolean;
    protected
        procedure setProtectionFlag(flagMask: int; flagValue: boolean); virtual;
        procedure setAddress(address: long); virtual;
        procedure setSize(size: long); virtual;
    public
        constructor create(); overload; override;
        constructor create(address: long; size: long; protectionFlags: int; module: UnicodeString = ''); overload; virtual;
        constructor create(address: Pointer; size: long; protectionFlags: int; module: UnicodeString = ''); overload; virtual;
        function allocate(): Pointer; virtual; overload;
        procedure deallocate(); virtual; overload;
        procedure protect(); virtual; overload;
        procedure setAddressSize(address, size: long); virtual;
    published
        property readable: boolean index pascalx.osapi.READABLE read getProtectionFlag write setProtectionFlag stored true;
        property writeable: boolean index pascalx.osapi.WRITEABLE read getProtectionFlag write setProtectionFlag stored true;
        property executable: boolean index pascalx.osapi.EXECUTABLE read getProtectionFlag write setProtectionFlag stored true;
        property address: long read fAddress write setAddress stored true;
        property size: long read fSize write setSize stored true;
        property module: UnicodeString read fModule write fModule stored isModuleStored;
    end;

    LocalFileSystem = class(_Object, ReadOnlyVirtualFileSystem, WriteableVirtualFileSystem)
    private
        class var instance: LocalFileSystem;
        class function isNameCorrect(const name: UnicodeString): boolean; static;
    public
        class function getInstance(): LocalFileSystem; static;
    private
        allowDestroy: boolean;
    public
        constructor create();
        function getObjectNameMaximumLength(): int;
        function findFirst(): FileEnumeration; overload;
        function findFirst(const objectName: UnicodeString): FileEnumeration; overload;
        function openFile(const fileName: UnicodeString): IOStream;
        function openFileForReading(const fileName: UnicodeString): InputStream;
        function openFileForAppending(const fileName: UnicodeString): OutputStream;
        function createFile(const fileName: UnicodeString): OutputStream;
        procedure createDirectory(const directoryName: UnicodeString);
        procedure deleteFile(const fileName: UnicodeString);
        procedure deleteDirectory(const directoryName: UnicodeString);
        procedure move(const oldObjectName, newObjectName: UnicodeString);
        procedure readAttributes(const objectName: UnicodeString; objectAttr: FileAttributes);
        procedure writeAttributes(const objectName: UnicodeString; objectAttr: FileAttributes);
        procedure beforeDestruction(); override;
    end;

    HandleInputStream = class(InputStream)
    protected
        handle: THandle;
    public
        constructor create(); overload;
        constructor create(handle: THandle); overload;
        function seekSupported(): boolean; override;
        function seek(delta: long): long; override;
        function reset(position: long = 0): long; override;
        function position(): long; override;
        function available(): long; override;
        function size(): long; override;
        function read(): int; override; overload;
        function read(const dst: byte_Array1d; offset, length: int): int; override; overload;
    end;

    HandleOutputStream = class(OutputStream)
    protected
        handle: THandle;
    public
        constructor create(); overload;
        constructor create(handle: THandle); overload;
        procedure flush(); override;
        procedure write(data: int); override; overload;
        procedure write(const src: byte_Array1d; offset, length: int); override; overload;
    end;

    FileInputStream = class(HandleInputStream)
    private
        fileName: UnicodeString;
    public
        constructor create(const fileName: AnsiString); overload;
        constructor create(const fileName: UnicodeString); overload;
        function getFileName(): UnicodeString;
        function hasOpenError(): boolean; virtual;
        procedure checkOpenError(); virtual;
        procedure close(); override;
    end;

    FileOutputStream = class(HandleOutputStream)
    private
        fileName: UnicodeString;
    public
        constructor create(const fileName: AnsiString; appending: boolean = false); overload;
        constructor create(const fileName: UnicodeString; appending: boolean = false); overload;
        function getFileName(): UnicodeString;
        function hasOpenError(): boolean; virtual;
        procedure checkOpenError(); virtual;
        procedure close(); override;
    end;

    FileIOStream = class(IOStream)
    private
        fileName: UnicodeString;
        inputPtr: HandleInputStream;
        inputIntf: Input;
        outputPtr: HandleOutputStream;
        outputIntf: Output;
    public
        constructor create(const fileName: AnsiString); overload;
        constructor create(const fileName: UnicodeString); overload;
        function seekSupported(): boolean; override;
        function truncateSupported(): boolean; override;
        function seek(delta: long): long; override;
        function reset(position: long = 0): long; override;
        function truncate(): long; override;
        function getInputStream(): InputStream; override;
        function getOutputStream(): OutputStream; override;
        function getFileName(): UnicodeString;
        function hasOpenError(): boolean; virtual;
        procedure checkOpenError(); virtual;
        procedure close(); override;
    end;
{%endregion}

implementation

{%region private }
type
    LocalDiskInfo = class;
    LocalDiskEnumeration = class;
    LocalFileEnumeration = class;
    FileIOStream0HandleInputStream = class;
    FileIOStream0HandleOutputStream = class;

    LocalDiskInfo = class(FileEnumeration)
    public
        constructor create(letter: wchar);
        function findNext(): boolean; override;
    end;

    LocalDiskEnumeration = class(LocalDiskInfo)
    private
        letter: wchar;
    public
        constructor create(letter: wchar);
        function findNext(): boolean; override;
    end;

    LocalFileEnumeration = class(FileEnumeration)
    private
        class function getFileTime(const fileTime: TFileTime): long; static;
        class function getFileSize(const findData: TWin32FindDataW): long; static;
        class function getFileName(const findData: TWin32FindDataW): UnicodeString; static;
        class procedure toFileTime(const timeInMillis: long; var fileTime: TFileTime); static;
    private
        handle: THandle;
        findData: TWin32FindDataW;
    public
        constructor create(handle: THandle; const findData: TWin32FindDataW);
        function findNext(): boolean; override;
        procedure close(); override;
    end;

    FileIOStream0HandleInputStream = class(HandleInputStream)
    private
        bufferedPosition: long;
    public
        function seek(delta: long): long; override;
        function reset(position: long = 0): long; override;
        function position(): long; override;
        function available(): long; override;
        function read(): int; override; overload;
        function read(const dst: byte_Array1d; offset, length: int): int; override; overload;
    end;

    FileIOStream0HandleOutputStream = class(HandleOutputStream)
    private
        bufferedPosition: long;
        function position(): long;
        procedure reset(position: long);
    public
        procedure write(data: int); override; overload;
        procedure write(const src: byte_Array1d; offset, length: int); override; overload;
    end;
{%endregion}

{%region MemoryRegion }
    class function MemoryRegion.getMinimumAddress(): long;
    begin
        result := $0000000000000000;
    end;

    class function MemoryRegion.getMaximumAddress(): long;
    begin
        result := $000007ffffffffff;
    end;

    class function MemoryRegion.allocate(address: long; size: long; protectionFlags: int): Pointer;
    begin
        result := allocate(Pointer((@address)^), size, protectionFlags);
    end;

    class function MemoryRegion.allocate(address: Pointer; size: long; protectionFlags: int): Pointer;
    var
        addr: long;
        lim1: long;
        lim2: long;
    begin
        if size < 0 then begin
            raise IllegalArgumentException.create('MemoryRegion.allocate: ' + msgIllegalArgument + 'size');
        end;
        if size = 0 then begin
            result := nil;
            exit;
        end;
        addr := long((@address)^);
        lim1 := getMinimumAddress();
        lim2 := getMaximumAddress() + 1;
        if (addr < lim1) or (addr > lim2) then begin
            raise IllegalArgumentException.create('MemoryRegion.allocate: ' + msgIllegalArgument + 'address');
        end;
        inc(addr, size);
        if (addr < lim1) or (addr > lim2) then begin
            raise IllegalArgumentException.create('MemoryRegion.allocate: ' + msgIllegalArgument + 'address');
        end;
        protectionFlags := (protectionFlags or pascalx.osapi.READABLE) and $07;
        case protectionFlags of
        pascalx.osapi.READABLE:
            protectionFlags := PAGE_READONLY;
        pascalx.osapi.READABLE or pascalx.osapi.WRITEABLE:
            protectionFlags := PAGE_READWRITE;
        pascalx.osapi.READABLE or pascalx.osapi.EXECUTABLE:
            protectionFlags := PAGE_EXECUTE_READ;
        else
            protectionFlags := PAGE_EXECUTE_READWRITE;
        end;
        result := virtualAlloc(address, size, MEM_COMMIT, protectionFlags);
    end;

    class procedure MemoryRegion.deallocate(address: Pointer; size: long);
    var
        addr: long;
        lim1: long;
        lim2: long;
    begin
        if size < 0 then begin
            raise IllegalArgumentException.create('MemoryRegion.deallocate: ' + msgIllegalArgument + 'size');
        end;
        if size = 0 then exit;
        addr := long((@address)^);
        lim1 := getMinimumAddress();
        lim2 := getMaximumAddress() + 1;
        if (addr < lim1) or (addr > lim2) then begin
            raise IllegalArgumentException.create('MemoryRegion.deallocate: ' + msgIllegalArgument + 'address');
        end;
        inc(addr, size);
        if (addr < lim1) or (addr > lim2) then begin
            raise IllegalArgumentException.create('MemoryRegion.deallocate: ' + msgIllegalArgument + 'address');
        end;
        virtualFree(address, size, MEM_DECOMMIT);
    end;

    class procedure MemoryRegion.protect(address: long; size: long; protectionFlags: int);
    begin
        protect(Pointer((@address)^), size, protectionFlags);
    end;

    class procedure MemoryRegion.protect(address: Pointer; size: long; protectionFlags: int);
    var
        addr: long;
        lim1: long;
        lim2: long;
        oldp: int;
    begin
        if size < 0 then begin
            raise IllegalArgumentException.create('MemoryRegion.protect: ' + msgIllegalArgument + 'size');
        end;
        if size = 0 then exit;
        addr := long((@address)^);
        lim1 := getMinimumAddress();
        lim2 := getMaximumAddress() + 1;
        if (addr < lim1) or (addr > lim2) then begin
            raise IllegalArgumentException.create('MemoryRegion.protect: ' + msgIllegalArgument + 'address');
        end;
        inc(addr, size);
        if (addr < lim1) or (addr > lim2) then begin
            raise IllegalArgumentException.create('MemoryRegion.protect: ' + msgIllegalArgument + 'address');
        end;
        protectionFlags := (protectionFlags or pascalx.osapi.READABLE) and $07;
        case protectionFlags of
        pascalx.osapi.READABLE:
            protectionFlags := PAGE_READONLY;
        pascalx.osapi.READABLE or pascalx.osapi.WRITEABLE:
            protectionFlags := PAGE_READWRITE;
        pascalx.osapi.READABLE or pascalx.osapi.EXECUTABLE:
            protectionFlags := PAGE_EXECUTE_READ;
        else
            protectionFlags := PAGE_EXECUTE_READWRITE;
        end;
        virtualProtect(address, size, protectionFlags, @oldp);
    end;

    class procedure MemoryRegion.getInfo(address: long; region: MemoryRegion);
    begin
        getInfo(Pointer((@address)^), region);
    end;

    class procedure MemoryRegion.getInfo(address: Pointer; region: MemoryRegion);
    var
        lim1: long;
        lim2: long;
        addr: long;
        size: long;
        queryAddress: long;
        allocationBase: Pointer;
        info: TMemoryBasicInformation;
        basicProtection: int;
        basicModule: UnicodeString;
        currProtection: int;
        currModule: UnicodeString;
        moduleStorage: PWideChar;
    begin
        addr := long((@address)^);
        lim1 := getMinimumAddress();
        lim2 := getMaximumAddress();
        if (addr < lim1) or (addr > lim2) then begin
            raise IllegalArgumentException.create('MemoryRegion.getInfo: ' + msgIllegalArgument + 'address');
        end;
        if region = nil then begin
            raise NullPointerException.create('MemoryRegion.getInfo: ' + msgNullPointerArgument + 'region');
        end;
        initialize(info);
        virtualQuery(address, info, sizeOf(TMemoryBasicInformation));
        addr := long((@info.baseAddress)^);
        size := 0;
        moduleStorage := getMemory((MAX_PATH + 1) shl 1);
        try
            moduleStorage^ := #$0000;
            allocationBase := info.allocationBase;
            if allocationBase <> nil then begin
                getModuleFileNameW(HInst((@allocationBase)^), moduleStorage, MAX_PATH);
            end;
            basicProtection := int(info.protect) and $fe;
            basicModule := UnicodeString(moduleStorage);
            repeat
                inc(size, long(info.regionSize));
                queryAddress := addr + size;
                if queryAddress > lim2 then break;
                virtualQuery(Pointer((@queryAddress)^), info, sizeOf(TMemoryBasicInformation));
                moduleStorage^ := #$0000;
                allocationBase := info.allocationBase;
                if allocationBase <> nil then begin
                    getModuleFileNameW(HInst((@allocationBase)^), moduleStorage, MAX_PATH);
                end;
                currProtection := int(info.protect) and $fe;
                currModule := UnicodeString(moduleStorage);
            until (currProtection <> basicProtection) or (currModule <> basicModule);
        finally
            freeMemory(moduleStorage);
        end;
        region.readable := basicProtection <> 0;
        region.writeable := (basicProtection and (PAGE_EXECUTE_READWRITE or PAGE_READWRITE or PAGE_EXECUTE_WRITECOPY or PAGE_WRITECOPY)) <> 0;
        region.executable := (basicProtection and (PAGE_EXECUTE or PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY)) <> 0;
        region.address := addr;
        region.size := size;
        region.module := basicModule;
    end;

    function MemoryRegion.isModuleStored(): boolean;
    begin
        result := length(fModule) > 0;
    end;

    function MemoryRegion.getProtectionFlag(flagMask: int): boolean;
    begin
        result := (fProtectionFlags and flagMask) <> 0;
    end;

    procedure MemoryRegion.setProtectionFlag(flagMask: int; flagValue: boolean);
    begin
        if flagValue then begin
            fProtectionFlags := (fProtectionFlags or flagMask) and $07;
        end else begin
            fProtectionFlags := (fProtectionFlags and not flagMask) and $07;
        end;
    end;

    procedure MemoryRegion.setAddress(address: long);
    begin
        address := longBound(getMinimumAddress(), address, getMaximumAddress() - fSize + 1);
        address := address and (-$1000);
        self.fAddress := address;
    end;

    procedure MemoryRegion.setSize(size: long);
    begin
        inc(size, (-size) and $0fff);
        size := longBound($1000, size, getMaximumAddress() - fAddress + 1);
        self.fSize := size;
    end;

    constructor MemoryRegion.create();
    begin
        create(0, $1000, 0, '');
    end;

    constructor MemoryRegion.create(address: long; size: long; protectionFlags: int; module: UnicodeString);
    var
        min: long;
        max: long;
    begin
        inherited create();
        min := getMinimumAddress();
        max := getMaximumAddress() + 1;
        inc(size, (-size) and $0fff);
        size := longBound($1000, size, max - min);
        address := longBound(min, address, max - size);
        address := address and (-$1000);
        self.fProtectionFlags := protectionFlags and $07;
        self.fAddress := address;
        self.fSize := size;
        self.fModule := module;
    end;

    constructor MemoryRegion.create(address: Pointer; size: long; protectionFlags: int; module: UnicodeString);
    begin
        create(long((@address)^), size, protectionFlags, module);
    end;

    function MemoryRegion.allocate(): Pointer;
    begin
        result := allocate(fAddress, fSize, fProtectionFlags);
        address := long((@result)^);
    end;

    procedure MemoryRegion.deallocate();
    begin
        deallocate(Pointer((@fAddress)^), fSize);
    end;

    procedure MemoryRegion.protect();
    begin
        protect(fAddress, fSize, fProtectionFlags);
    end;

    procedure MemoryRegion.setAddressSize(address, size: long);
    var
        min: long;
        max: long;
    begin
        min := getMinimumAddress();
        max := getMaximumAddress() + 1;
        inc(size, (-size) and $0fff);
        size := longBound($1000, size, max - min);
        address := longBound(min, address, max - size);
        address := address and (-$1000);
        self.fAddress := address;
        self.fSize := size;
    end;
{%endregion}

{%region LocalFileSystem }
    class function LocalFileSystem.isNameCorrect(const name: UnicodeString): boolean;
    begin
        result := not stringEndsWith(wchar(DIRECTORY_SEPARATOR) + '.', name) and not stringEndsWith(wchar(DIRECTORY_SEPARATOR) + '..', name);
        result := result and (stringIndexOf(wchar(DIRECTORY_SEPARATOR) + '..' + DIRECTORY_SEPARATOR, name) = 0);
        result := result and (stringIndexOf(wchar(DIRECTORY_SEPARATOR) + '.' + DIRECTORY_SEPARATOR, name) = 0);
        result := result and (stringIndexOf(wchar(DIRECTORY_SEPARATOR) + '' + DIRECTORY_SEPARATOR, name) = 0);
        result := result and (stringIndexOf(wchar('|'), name) = 0) and (stringIndexOf(wchar('"'), name) = 0);
        result := result and (stringIndexOf(wchar('<'), name) = 0) and (stringIndexOf(wchar('>'), name) = 0);
        result := result and (stringIndexOf(wchar('*'), name) = 0) and (stringIndexOf(wchar('?'), name) = 0);
        result := result and (stringLastIndexOf(wchar(':'), name) = 2);
    end;

    class function LocalFileSystem.getInstance(): LocalFileSystem;
    begin
        result := instance;
    end;

    constructor LocalFileSystem.create();
    begin
        inherited create();
        self.allowDestroy := true;
    end;

    function LocalFileSystem.getObjectNameMaximumLength(): int;
    begin
        result := MAX_PATH - 1;
    end;

    function LocalFileSystem.findFirst(): FileEnumeration;
    var
        find: UnicodeString;
        letter: wchar;
        availableBytes: long;
        totalFreeBytes: long;
        totalSizeInBytes: long;
    begin
        for letter := 'A' to 'Z' do begin
            find := letter + (':' + DIRECTORY_SEPARATOR);
            if getDiskFreeSpaceExW(PWideChar(find), @availableBytes, @totalSizeInBytes, @totalFreeBytes) then begin
                result := LocalDiskEnumeration.create(letter);
                exit;
            end;
        end;
        result := nil;
    end;

    function LocalFileSystem.findFirst(const objectName: UnicodeString): FileEnumeration;
    var
        name: UnicodeString;
        find: UnicodeString;
        found: UnicodeString;
        letter: wchar;
        len: int;
        availableBytes: long;
        totalFreeBytes: long;
        totalSizeInBytes: long;
        correctName: boolean;
        handleInUse: boolean;
        handle: THandle;
        findData: TWin32FindDataW;
    begin
        name := stringReplace(objectName, '/', DIRECTORY_SEPARATOR);
        if stringStartsWith(wchar(DIRECTORY_SEPARATOR), name) then begin
            name := stringCopy(name, 2);
        end;
        len := length(name);
        if len <= 0 then begin
            for letter := 'A' to 'Z' do begin
                find := letter + (':' + DIRECTORY_SEPARATOR);
                if getDiskFreeSpaceExW(PWideChar(find), @availableBytes, @totalSizeInBytes, @totalFreeBytes) then begin
                    result := LocalDiskEnumeration.create(letter);
                    exit;
                end;
            end;
            result := nil;
            exit;
        end;
        letter := name[1];
        correctName := (len >= 2) and ((letter >= 'A') and (letter <= 'Z') or (letter >= 'a') and (letter <= 'z')) and isNameCorrect(name);
        if name[len] = DIRECTORY_SEPARATOR then begin
            name := stringCopy(name, 1, length(name));
            if not correctName then begin
                raise DirectoryNotFoundException.create(name, 'LocalFileSystem.findFirst: ' + msgDirectoryNotFound);
            end;
            find := name + (DIRECTORY_SEPARATOR + '*');
            initialize(findData);
            handleInUse := false;
            handle := findFirstFileW(PWideChar(find), findData);
            if handle = THandle(-1) then begin
                if int(getFileAttributesW(PWideChar(name))) <> -1 then begin
                    raise FileSystemSecurityException.create(name, 'LocalFileSystem.findFirst: ' + msgSecurity);
                end;
                raise DirectoryNotFoundException.create(name, 'LocalFileSystem.findFirst: ' + msgDirectoryNotFound);
            end;
            try
                repeat
                    found := UnicodeString(PWideChar(@findData.cFileName));
                    if (found <> '.') and (found <> '..') then break;
                    if not findNextFileW(handle, findData) then begin
                        result := nil;
                        exit;
                    end;
                until false;
                handleInUse := true;
            finally
                if not handleInUse then findClose(handle);
            end;
            result := LocalFileEnumeration.create(handle, findData);
            exit;
        end;
        if not correctName then begin
            result := nil;
            exit;
        end;
        if len = 2 then begin
            letter := charToUpperCase(letter);
            find := letter + (':' + DIRECTORY_SEPARATOR);
            if getDiskFreeSpaceExW(PWideChar(find), @availableBytes, @totalSizeInBytes, @totalFreeBytes) then begin
                result := LocalDiskInfo.create(letter);
                exit;
            end;
            result := nil;
            exit;
        end;
        initialize(findData);
        handleInUse := false;
        handle := findFirstFileW(PWideChar(name), findData);
        if handle = THandle(-1) then begin
            result := nil;
            exit;
        end;
        try
            repeat
                found := UnicodeString(PWideChar(@findData.cFileName));
                if (found <> '.') and (found <> '..') then break;
                if not findNextFileW(handle, findData) then begin
                    result := nil;
                    exit;
                end;
            until false;
            handleInUse := true;
        finally
            if not handleInUse then findClose(handle);
        end;
        result := LocalFileEnumeration.create(handle, findData);
    end;

    function LocalFileSystem.openFile(const fileName: UnicodeString): IOStream;
    var
        name: UnicodeString;
        letter: wchar;
        len: int;
        correctName: boolean;
        availableBytes: long;
        totalFreeBytes: long;
        totalSizeInBytes: long;
        attributes: int;
        handle: THandle;
        stream: FileIOStream;
        instrm: HandleInputStream;
        outstrm: HandleOutputStream;
    begin
        name := stringReplace(fileName, '/', DIRECTORY_SEPARATOR);
        if stringStartsWith(wchar(DIRECTORY_SEPARATOR), name) then begin
            name := stringCopy(name, 2);
        end;
        len := length(name);
        if len <= 0 then begin
            raise FileNotFoundException.create(name, 'LocalFileSystem.openFile: ' + msgFileNotFound);
        end;
        letter := name[1];
        correctName := (len >= 2) and ((letter >= 'A') and (letter <= 'Z') or (letter >= 'a') and (letter <= 'z')) and isNameCorrect(name);
        if name[len] = DIRECTORY_SEPARATOR then begin
            name := stringCopy(name, 1, len);
            dec(len);
        end;
        if not correctName then begin
            raise FileNotFoundException.create(name, 'LocalFileSystem.openFile: ' + msgFileNotFound);
        end;
        if len = 2 then begin
            name := letter + (':' + DIRECTORY_SEPARATOR);
            if getDiskFreeSpaceExW(PWideChar(name), @availableBytes, @totalSizeInBytes, @totalFreeBytes) then begin
                raise FileCreationException.create(name, 'LocalFileSystem.openFile: ' + msgFileCreationError);
            end;
            raise FileNotFoundException.create(name, 'LocalFileSystem.openFile: ' + msgFileNotFound);
        end;
        attributes := getFileAttributesW(PWideChar(name));
        if attributes = -1 then begin
            raise FileNotFoundException.create(name, 'LocalFileSystem.openFile: ' + msgFileNotFound);
        end;
        if (attributes and ATTR_DIRECTORY) <> 0 then begin
            raise FileCreationException.create(name, 'LocalFileSystem.openFile: ' + msgFileCreationError);
        end;
        handle := createFileW(PWideChar(name), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, attributes, 0);
        if handle = THandle(-1) then begin
            raise FileSystemSecurityException.create(name, 'LocalFileSystem.openFile: ' + msgSecurity);
        end;
        stream := FileIOStream.create();
        instrm := FileIOStream0HandleInputStream.create(handle);
        outstrm := FileIOStream0HandleOutputStream.create(handle);
        stream.fileName := name;
        stream.inputPtr := instrm;
        stream.inputIntf := instrm;
        stream.outputPtr := outstrm;
        stream.outputIntf := outstrm;
        result := stream;
    end;

    function LocalFileSystem.openFileForReading(const fileName: UnicodeString): InputStream;
    var
        name: UnicodeString;
        letter: wchar;
        len: int;
        correctName: boolean;
        availableBytes: long;
        totalFreeBytes: long;
        totalSizeInBytes: long;
        attributes: int;
        handle: THandle;
        stream: FileInputStream;
    begin
        name := stringReplace(fileName, '/', DIRECTORY_SEPARATOR);
        if stringStartsWith(wchar(DIRECTORY_SEPARATOR), name) then begin
            name := stringCopy(name, 2);
        end;
        len := length(name);
        if len <= 0 then begin
            raise FileNotFoundException.create(name, 'LocalFileSystem.openFileForReading: ' + msgFileNotFound);
        end;
        letter := name[1];
        correctName := (len >= 2) and ((letter >= 'A') and (letter <= 'Z') or (letter >= 'a') and (letter <= 'z')) and isNameCorrect(name);
        if name[len] = DIRECTORY_SEPARATOR then begin
            name := stringCopy(name, 1, len);
            dec(len);
        end;
        if not correctName then begin
            raise FileNotFoundException.create(name, 'LocalFileSystem.openFileForReading: ' + msgFileNotFound);
        end;
        if len = 2 then begin
            name := letter + (':' + DIRECTORY_SEPARATOR);
            if getDiskFreeSpaceExW(PWideChar(name), @availableBytes, @totalSizeInBytes, @totalFreeBytes) then begin
                raise FileCreationException.create(name, 'LocalFileSystem.openFileForReading: ' + msgFileCreationError);
            end;
            raise FileNotFoundException.create(name, 'LocalFileSystem.openFileForReading: ' + msgFileNotFound);
        end;
        attributes := getFileAttributesW(PWideChar(name));
        if attributes = -1 then begin
            raise FileNotFoundException.create(name, 'LocalFileSystem.openFileForReading: ' + msgFileNotFound);
        end;
        if (attributes and ATTR_DIRECTORY) <> 0 then begin
            raise FileCreationException.create(name, 'LocalFileSystem.openFileForReading: ' + msgFileCreationError);
        end;
        handle := createFileW(PWideChar(name), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, attributes, 0);
        if handle = THandle(-1) then begin
            raise FileSystemSecurityException.create(name, 'LocalFileSystem.openFileForReading: ' + msgSecurity);
        end;
        stream := FileInputStream.create(handle);
        stream.fileName := name;
        result := stream;
    end;

    function LocalFileSystem.openFileForAppending(const fileName: UnicodeString): OutputStream;
    var
        name: UnicodeString;
        letter: wchar;
        len: int;
        correctName: boolean;
        availableBytes: long;
        totalFreeBytes: long;
        totalSizeInBytes: long;
        attributes: int;
        position: int;
        handle: THandle;
        stream: FileOutputStream;
    begin
        name := stringReplace(fileName, '/', DIRECTORY_SEPARATOR);
        if stringStartsWith(wchar(DIRECTORY_SEPARATOR), name) then begin
            name := stringCopy(name, 2);
        end;
        len := length(name);
        if len <= 0 then begin
            raise FileNotFoundException.create(name, 'LocalFileSystem.openFileForAppending: ' + msgFileNotFound);
        end;
        letter := name[1];
        correctName := (len >= 2) and ((letter >= 'A') and (letter <= 'Z') or (letter >= 'a') and (letter <= 'z')) and isNameCorrect(name);
        if name[len] = DIRECTORY_SEPARATOR then begin
            name := stringCopy(name, 1, len);
            dec(len);
        end;
        if not correctName then begin
            raise FileNotFoundException.create(name, 'LocalFileSystem.openFileForAppending: ' + msgFileNotFound);
        end;
        if len = 2 then begin
            name := letter + (':' + DIRECTORY_SEPARATOR);
            if getDiskFreeSpaceExW(PWideChar(name), @availableBytes, @totalSizeInBytes, @totalFreeBytes) then begin
                raise FileCreationException.create(name, 'LocalFileSystem.openFileForAppending: ' + msgFileCreationError);
            end;
            raise FileNotFoundException.create(name, 'LocalFileSystem.openFileForAppending: ' + msgFileNotFound);
        end;
        attributes := getFileAttributesW(PWideChar(name));
        if attributes = -1 then begin
            raise FileNotFoundException.create(name, 'LocalFileSystem.openFileForAppending: ' + msgFileNotFound);
        end;
        if (attributes and ATTR_DIRECTORY) <> 0 then begin
            raise FileCreationException.create(name, 'LocalFileSystem.openFileForAppending: ' + msgFileCreationError);
        end;
        handle := createFileW(PWideChar(name), GENERIC_WRITE, 0, nil, OPEN_EXISTING, attributes, 0);
        if handle = THandle(-1) then begin
            raise FileSystemSecurityException.create(name, 'LocalFileSystem.openFileForAppending: ' + msgSecurity);
        end;
        position := 0;
        setFilePointer(handle, 0, @position, FILE_END);
        stream := FileOutputStream.create(handle);
        stream.fileName := name;
        result := stream;
    end;

    function LocalFileSystem.createFile(const fileName: UnicodeString): OutputStream;
    var
        name: UnicodeString;
        letter: wchar;
        len: int;
        correctName: boolean;
        attributes: int;
        handle: THandle;
        stream: FileOutputStream;
    begin
        name := stringReplace(fileName, '/', DIRECTORY_SEPARATOR);
        if stringStartsWith(wchar(DIRECTORY_SEPARATOR), name) then begin
            name := stringCopy(name, 2);
        end;
        len := length(name);
        if len <= 0 then begin
            raise FileCreationException.create(name, 'LocalFileSystem.createFile: ' + msgFileCreationError);
        end;
        letter := name[1];
        correctName := (len >= 2) and ((letter >= 'A') and (letter <= 'Z') or (letter >= 'a') and (letter <= 'z')) and isNameCorrect(name);
        if name[len] = DIRECTORY_SEPARATOR then begin
            name := stringCopy(name, 1, len);
            dec(len);
        end;
        if not correctName or (len = 2) then begin
            raise FileCreationException.create(name, 'LocalFileSystem.createFile: ' + msgFileCreationError);
        end;
        attributes := int(getFileAttributesW(PWideChar(name)));
        if (attributes <> -1) and ((attributes and ATTR_DIRECTORY) <> 0) then begin
            raise FileCreationException.create(name, 'LocalFileSystem.createFile: ' + msgFileCreationError);
        end;
        if (attributes <> -1) and ((attributes and ATTR_READONLY) <> 0) then begin
            raise FileSystemSecurityException.create(name, 'LocalFileSystem.createFile: ' + msgSecurity);
        end;
        handle := createFileW(PWideChar(name), GENERIC_WRITE, 0, nil, CREATE_ALWAYS, 0, 0);
        if handle = THandle(-1) then begin
            raise FileSystemSecurityException.create(name, 'LocalFileSystem.createFile: ' + msgSecurity);
        end;
        stream := FileOutputStream.create(handle);
        stream.fileName := name;
        result := stream;
    end;

    procedure LocalFileSystem.createDirectory(const directoryName: UnicodeString);
    var
        name: UnicodeString;
        letter: wchar;
        len: int;
        correctName: boolean;
    begin
        name := stringReplace(directoryName, '/', DIRECTORY_SEPARATOR);
        if stringStartsWith(wchar(DIRECTORY_SEPARATOR), name) then begin
            name := stringCopy(name, 2);
        end;
        len := length(name);
        if len <= 0 then begin
            raise DirectoryCreationException.create(name, 'LocalFileSystem.createDirectory: ' + msgDirectoryCreationError);
        end;
        letter := name[1];
        correctName := (len >= 2) and ((letter >= 'A') and (letter <= 'Z') or (letter >= 'a') and (letter <= 'z')) and isNameCorrect(name);
        if name[len] = DIRECTORY_SEPARATOR then begin
            name := stringCopy(name, 1, len);
            dec(len);
        end;
        if not correctName or (len = 2) or (int(getFileAttributesW(PWideChar(name))) <> -1) or not createDirectoryW(PWideChar(name), nil) then begin
            raise DirectoryCreationException.create(name, 'LocalFileSystem.createDirectory: ' + msgDirectoryCreationError);
        end;
    end;

    procedure LocalFileSystem.deleteFile(const fileName: UnicodeString);
    var
        name: UnicodeString;
        letter: wchar;
        len: int;
        attributes: int;
        correctName: boolean;
    begin
        name := stringReplace(fileName, '/', DIRECTORY_SEPARATOR);
        if stringStartsWith(wchar(DIRECTORY_SEPARATOR), name) then begin
            name := stringCopy(name, 2);
        end;
        len := length(name);
        if len <= 0 then begin
            raise FileNotFoundException.create(name, 'LocalFileSystem.deleteFile: ' + msgFileNotFound);
        end;
        letter := name[1];
        correctName := (len >= 2) and ((letter >= 'A') and (letter <= 'Z') or (letter >= 'a') and (letter <= 'z')) and isNameCorrect(name);
        if name[len] = DIRECTORY_SEPARATOR then begin
            name := stringCopy(name, 1, len);
            dec(len);
        end;
        if not correctName or (len = 2) then begin
            raise FileNotFoundException.create(name, 'LocalFileSystem.deleteFile: ' + msgFileNotFound);
        end;
        attributes := int(getFileAttributesW(PWideChar(name)));
        if (attributes = -1) or ((attributes and ATTR_DIRECTORY) <> 0) then begin
            raise FileNotFoundException.create(name, 'LocalFileSystem.deleteFile: ' + msgFileNotFound);
        end;
        if not deleteFileW(PWideChar(name)) then begin
            raise FileDeletionException.create(name, 'LocalFileSystem.deleteFile: ' + msgFileDeletionError);
        end;
    end;

    procedure LocalFileSystem.deleteDirectory(const directoryName: UnicodeString);
    var
        name: UnicodeString;
        letter: wchar;
        len: int;
        attributes: int;
        correctName: boolean;
    begin
        name := stringReplace(directoryName, '/', DIRECTORY_SEPARATOR);
        if stringStartsWith(wchar(DIRECTORY_SEPARATOR), name) then begin
            name := stringCopy(name, 2);
        end;
        len := length(name);
        if len <= 0 then begin
            raise DirectoryNotFoundException.create(name, 'LocalFileSystem.deleteDirectory: ' + msgDirectoryNotFound);
        end;
        letter := name[1];
        correctName := (len >= 2) and ((letter >= 'A') and (letter <= 'Z') or (letter >= 'a') and (letter <= 'z')) and isNameCorrect(name);
        if name[len] = DIRECTORY_SEPARATOR then begin
            name := stringCopy(name, 1, len);
            dec(len);
        end;
        if not correctName then begin
            raise DirectoryNotFoundException.create(name, 'LocalFileSystem.deleteDirectory: ' + msgDirectoryNotFound);
        end;
        if len = 2 then begin
            raise DirectoryDeletionException.create(name, 'LocalFileSystem.deleteDirectory: ' + msgDirectoryDeletionError);
        end;
        attributes := int(getFileAttributesW(PWideChar(name)));
        if (attributes = -1) or ((attributes and ATTR_DIRECTORY) = 0) then begin
            raise DirectoryNotFoundException.create(name, 'LocalFileSystem.deleteDirectory: ' + msgDirectoryNotFound);
        end;
        if not removeDirectoryW(PWideChar(name)) then begin
            raise DirectoryDeletionException.create(name, 'LocalFileSystem.deleteDirectory: ' + msgDirectoryDeletionError);
        end;
    end;

    procedure LocalFileSystem.move(const oldObjectName, newObjectName: UnicodeString);
    var
        oldName: UnicodeString;
        oldLetter: wchar;
        oldLen: int;
        oldCorrectName: boolean;
        newName: UnicodeString;
        newLetter: wchar;
        newLen: int;
        newCorrectName: boolean;
    begin
        oldName := stringReplace(oldObjectName, '/', DIRECTORY_SEPARATOR);
        newName := stringReplace(newObjectName, '/', DIRECTORY_SEPARATOR);
        if stringStartsWith(wchar(DIRECTORY_SEPARATOR), oldName) then begin
            oldName := stringCopy(oldName, 2);
        end;
        if stringStartsWith(wchar(DIRECTORY_SEPARATOR), newName) then begin
            newName := stringCopy(newName, 2);
        end;
        oldLen := length(oldName);
        newLen := length(newName);
        if (oldLen <= 0) or (newLen <= 0) then begin
            raise MoveOperationException.create(oldName, newName, 'LocalFileSystem.move: ' + msgMoveError);
        end;
        oldLetter := oldName[1];
        newLetter := newName[1];
        oldCorrectName := (oldLen >= 2) and ((oldLetter >= 'A') and (oldLetter <= 'Z') or (oldLetter >= 'a') and (oldLetter <= 'z')) and isNameCorrect(oldName);
        newCorrectName := (newLen >= 2) and ((newLetter >= 'A') and (newLetter <= 'Z') or (newLetter >= 'a') and (newLetter <= 'z')) and isNameCorrect(newName);
        if oldName[oldLen] = DIRECTORY_SEPARATOR then begin
            oldName := stringCopy(oldName, 1, oldLen);
            dec(oldLen);
        end;
        if newName[newLen] = DIRECTORY_SEPARATOR then begin
            newName := stringCopy(newName, 1, newLen);
            dec(newLen);
        end;
        if not oldCorrectName or not newCorrectName or (oldLen = 2) or (newLen = 2) or not moveFileW(PWideChar(oldName), PWideChar(newName)) then begin
            raise MoveOperationException.create(oldName, newName, 'LocalFileSystem.move: ' + msgMoveError);
        end;
    end;

    procedure LocalFileSystem.readAttributes(const objectName: UnicodeString; objectAttr: FileAttributes);
    var
        name: UnicodeString;
        find: UnicodeString;
        letter: wchar;
        len: int;
        attributes: int;
        availableBytes: long;
        totalFreeBytes: long;
        totalSizeInBytes: long;
        correctName: boolean;
        handle: THandle;
        creationTime: TFileTime;
        lastWriteTime: TFileTime;
        lastAccessTime: TFileTime;
        findData: TWin32FindDataW;
    begin
        name := stringReplace(objectName, '/', DIRECTORY_SEPARATOR);
        if stringStartsWith(wchar(DIRECTORY_SEPARATOR), name) then begin
            name := stringCopy(name, 2);
        end;
        len := length(name);
        if len <= 0 then begin
            raise ObjectNotFoundException.create(name, 'LocalFileSystem.readAttributes: ' + msgObjectNotFound);
        end;
        letter := name[1];
        correctName := (len >= 2) and ((letter >= 'A') and (letter <= 'Z') or (letter >= 'a') and (letter <= 'z')) and isNameCorrect(name);
        if name[len] = DIRECTORY_SEPARATOR then begin
            name := stringCopy(name, 1, len);
            dec(len);
        end;
        if not correctName then begin
            raise ObjectNotFoundException.create(name, 'LocalFileSystem.readAttributes: ' + msgObjectNotFound);
        end;
        if len = 2 then begin
            letter := charToUpperCase(letter);
            find := letter + (':' + DIRECTORY_SEPARATOR);
            if not getDiskFreeSpaceExW(PWideChar(find), @availableBytes, @totalSizeInBytes, @totalFreeBytes) then begin
                raise ObjectNotFoundException.create(name, 'LocalFileSystem.readAttributes: ' + msgObjectNotFound);
            end;
            if objectAttr <> nil then begin
                objectAttr.setAttributes(ATTR_DIRECTORY, LONG_MIN_VALUE, LONG_MIN_VALUE, LONG_MIN_VALUE);
            end;
            exit;
        end;
        attributes := int(getFileAttributesW(PWideChar(name)));
        if attributes = -1 then begin
            raise ObjectNotFoundException.create(name, 'LocalFileSystem.readAttributes: ' + msgObjectNotFound);
        end;
        initialize(findData);
        handle := findFirstFileW(PWideChar(name), findData);
        if handle = THandle(-1) then begin
            raise FileSystemSecurityException.create(name, 'LocalFileSystem.readAttributes: ' + msgSecurity);
        end;
        try
            creationTime := findData.ftCreationTime;
            lastWriteTime := findData.ftLastWriteTime;
            lastAccessTime := findData.ftLastAccessTime;
        finally
            findClose(handle);
        end;
        if objectAttr <> nil then begin
            objectAttr.setAttributes(attributes, LocalFileEnumeration.getFileTime(creationTime), LocalFileEnumeration.getFileTime(lastWriteTime), LocalFileEnumeration.getFileTime(lastAccessTime));
        end;
    end;

    procedure LocalFileSystem.writeAttributes(const objectName: UnicodeString; objectAttr: FileAttributes);
    const
        ATTR_REWRITEABLE = ATTR_READONLY or ATTR_HIDDEN or ATTR_SYSTEM or ATTR_ARCHIVE;
    var
        name: UnicodeString;
        find: UnicodeString;
        letter: wchar;
        len: int;
        attributes: int;
        availableBytes: long;
        totalFreeBytes: long;
        totalSizeInBytes: long;
        correctName: boolean;
        handle: THandle;
        creationTime: TFileTime;
        lastWriteTime: TFileTime;
        lastAccessTime: TFileTime;
    begin
        name := stringReplace(objectName, '/', DIRECTORY_SEPARATOR);
        if stringStartsWith(wchar(DIRECTORY_SEPARATOR), name) then begin
            name := stringCopy(name, 2);
        end;
        len := length(name);
        if len <= 0 then begin
            raise ObjectNotFoundException.create(name, 'LocalFileSystem.writeAttributes: ' + msgObjectNotFound);
        end;
        letter := name[1];
        correctName := (len >= 2) and ((letter >= 'A') and (letter <= 'Z') or (letter >= 'a') and (letter <= 'z')) and isNameCorrect(name);
        if name[len] = DIRECTORY_SEPARATOR then begin
            name := stringCopy(name, 1, len);
            dec(len);
        end;
        if not correctName then begin
            raise ObjectNotFoundException.create(name, 'LocalFileSystem.writeAttributes: ' + msgObjectNotFound);
        end;
        if len = 2 then begin
            letter := charToUpperCase(letter);
            find := letter + (':' + DIRECTORY_SEPARATOR);
            if not getDiskFreeSpaceExW(PWideChar(find), @availableBytes, @totalSizeInBytes, @totalFreeBytes) then begin
                raise ObjectNotFoundException.create(name, 'LocalFileSystem.writeAttributes: ' + msgObjectNotFound);
            end;
            raise FileSystemSecurityException.create(name, 'LocalFileSystem.writeAttributes: ' + msgSecurity);
        end;
        attributes := int(getFileAttributesW(PWideChar(name)));
        if attributes = -1 then begin
            raise ObjectNotFoundException.create(name, 'LocalFileSystem.writeAttributes: ' + msgObjectNotFound);
        end;
        handle := createFileW(PWideChar(name), GENERIC_WRITE, 0, nil, OPEN_EXISTING, attributes or FILE_FLAG_BACKUP_SEMANTICS, 0);
        if handle = THandle(-1) then begin
            raise FileSystemSecurityException.create(name, 'LocalFileSystem.writeAttributes: ' + msgSecurity);
        end;
        try
            if objectAttr = nil then begin
                raise NullPointerException.create('LocalFileSystem.writeAttributes: ' + msgNullPointerArgument + 'objectAttr');
            end;
            initialize(creationTime);
            initialize(lastWriteTime);
            initialize(lastAccessTime);
            LocalFileEnumeration.toFileTime(objectAttr.getCreationTime(), creationTime);
            LocalFileEnumeration.toFileTime(objectAttr.getLastWriteTime(), lastWriteTime);
            LocalFileEnumeration.toFileTime(objectAttr.getLastAccessTime(), lastAccessTime);
            setFileTime(handle, @creationTime, @lastAccessTime, @lastWriteTime);
        finally
            closeHandle(handle);
        end;
        setFileAttributesW(PWideChar(name), (attributes and not ATTR_REWRITEABLE) or (objectAttr.getAttributes() and ATTR_REWRITEABLE));
    end;

    procedure LocalFileSystem.beforeDestruction();
    begin
        if not allowDestroy then begin
            raise RuntimeException.create('Error 204 (LocalFileSystem.getInstance() is indestructible)');
        end;
    end;
{%endregion}

{%region HandleInputStream }
    constructor HandleInputStream.create();
    begin
        inherited create();
        self.handle := THandle(-1);
    end;

    constructor HandleInputStream.create(handle: THandle);
    begin
        inherited create();
        self.handle := handle;
    end;

    function HandleInputStream.seekSupported(): boolean;
    begin
        result := true;
    end;

    function HandleInputStream.seek(delta: long): long;
    var
        poshi: int;
        poslo: int;
        handle: THandle;
    begin
        handle := self.handle;
        if handle = THandle(-1) then begin
            raise IOException.create('HandleInputStream.seek: ' + msgFileReadError);
        end;
        poshi := int(delta shr 32);
        poslo := setFilePointer(handle, int(delta), @poshi, FILE_CURRENT);
        result := (long(poshi) shl 32) or (long(poslo) and $00000000ffffffff);
    end;

    function HandleInputStream.reset(position: long): long;
    var
        poshi: int;
        poslo: int;
        handle: THandle;
    begin
        handle := self.handle;
        if handle = THandle(-1) then begin
            raise IOException.create('HandleInputStream.reset: ' + msgFileReadError);
        end;
        poshi := int(position shr 32);
        poslo := setFilePointer(handle, int(position), @poshi, FILE_BEGIN);
        result := (long(poshi) shl 32) or (long(poslo) and $00000000ffffffff);
    end;

    function HandleInputStream.position(): long;
    var
        poshi: int;
        poslo: int;
        handle: THandle;
    begin
        handle := self.handle;
        if handle = THandle(-1) then begin
            raise IOException.create('HandleInputStream.position: ' + msgFileReadError);
        end;
        poshi := 0;
        poslo := setFilePointer(handle, 0, @poshi, FILE_CURRENT);
        result := (long(poshi) shl 32) or (long(poslo) and $00000000ffffffff);
    end;

    function HandleInputStream.available(): long;
    var
        sizhi: int;
        sizlo: int;
        poshi: int;
        poslo: int;
        handle: THandle;
    begin
        handle := self.handle;
        if handle = THandle(-1) then begin
            raise IOException.create('HandleInputStream.available: ' + msgFileReadError);
        end;
        sizhi := 0;
        sizlo := getFileSize(handle, @sizhi);
        poshi := 0;
        poslo := setFilePointer(handle, 0, @poshi, FILE_CURRENT);
        result := ((long(sizhi) shl 32) or (long(sizlo) and $00000000ffffffff)) - ((long(poshi) shl 32) or (long(poslo) and $00000000ffffffff));
    end;

    function HandleInputStream.size(): long;
    var
        sizhi: int;
        sizlo: int;
        handle: THandle;
    begin
        handle := self.handle;
        if handle = THandle(-1) then begin
            raise IOException.create('HandleInputStream.size: ' + msgFileReadError);
        end;
        sizhi := 0;
        sizlo := getFileSize(handle, @sizhi);
        result := (long(sizhi) shl 32) or (long(sizlo) and $00000000ffffffff);
    end;

    function HandleInputStream.read(): int;
    var
        buffer: int;
        readed: int;
    begin
        buffer := 0;
        readed := 0;
        if not readFile(handle, buffer, 1, System.UInt32(readed), nil) then begin
            raise IOException.create('HandleInputStream.read: ' + msgFileReadError);
        end;
        if readed = 0 then begin
            result := -1;
            exit;
        end;
        result := buffer and $ff;
    end;

    function HandleInputStream.read(const dst: byte_Array1d; offset, length: int): int;
    var
        readed: int;
    begin
        arrayCheckBounds('HandleInputStream.read', System.length(dst), offset, length);
        readed := 0;
        if not readFile(handle, dst[offset], length, System.UInt32(readed), nil) then begin
            raise IOException.create('HandleInputStream.read: ' + msgFileReadError);
        end;
        if (length > 0) and (readed = 0) then begin
            result := -1;
            exit;
        end;
        result := readed;
    end;
{%endregion}

{%region HandleOutputStream }
    constructor HandleOutputStream.create();
    begin
        inherited create();
        self.handle := THandle(-1);
    end;

    constructor HandleOutputStream.create(handle: THandle);
    begin
        inherited create();
        self.handle := handle;
    end;

    procedure HandleOutputStream.flush();
    begin
        if not flushFileBuffers(handle) then begin
            raise IOException.create('HandleOutputStream.flush: ' + msgFileWriteError);
        end;
    end;

    procedure HandleOutputStream.write(data: int);
    var
        written: int;
    begin
        written := 0;
        if not writeFile(handle, data, 1, System.UInt32(written), nil) or (written <> 1) then begin
            raise IOException.create('HandleOutputStream.write: ' + msgFileWriteError);
        end;
    end;

    procedure HandleOutputStream.write(const src: byte_Array1d; offset, length: int);
    var
        written: int;
    begin
        arrayCheckBounds('HandleOutputStream.write', System.length(src), offset, length);
        written := 0;
        if not writeFile(handle, src[offset], length, System.UInt32(written), nil) or (written <> length) then begin
            raise IOException.create('HandleOutputStream.write: ' + msgFileWriteError);
        end;
    end;
{%endregion}

{%region FileInputStream }
    constructor FileInputStream.create(const fileName: AnsiString);
    begin
        create(stringToUTF16(fileName));
    end;

    constructor FileInputStream.create(const fileName: UnicodeString);
    begin
        inherited create();
        self.handle := createFileW(PWideChar(fileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, getFileAttributesW(PWideChar(fileName)), 0);
        self.fileName := fileName;
    end;

    function FileInputStream.getFileName(): UnicodeString;
    begin
        result := fileName;
    end;

    function FileInputStream.hasOpenError(): boolean;
    begin
        result := handle = THandle(-1);
    end;

    procedure FileInputStream.checkOpenError();
    begin
        if handle = THandle(-1) then begin
            raise FileCreationException.create(fileName, 'FileInputStream.checkOpenError: ' + msgFileCreationError);
        end;
    end;

    procedure FileInputStream.close();
    begin
        closeHandle(handle);
        handle := THandle(-1);
        inherited close();
    end;
{%endregion}

{%region FileOutputStream }
    constructor FileOutputStream.create(const fileName: AnsiString; appending: boolean);
    begin
        create(stringToUTF16(fileName), appending);
    end;

    constructor FileOutputStream.create(const fileName: UnicodeString; appending: boolean);
    var
        handle: THandle;
        pos: long;
    begin
        inherited create();
        if appending then begin
            handle := createFileW(PWideChar(fileName), GENERIC_WRITE, 0, nil, OPEN_EXISTING, getFileAttributesW(PWideChar(fileName)), 0);
            if handle <> THandle(-1) then begin
                pos := 0;
                setFilePointer(handle, int(pos), Pointer(@pos) + 4, FILE_END);
            end;
        end else begin
            handle := createFileW(PWideChar(fileName), GENERIC_WRITE, 0, nil, CREATE_ALWAYS, 0, 0);
        end;
        self.handle := handle;
        self.fileName := fileName;
    end;

    function FileOutputStream.getFileName(): UnicodeString;
    begin
        result := fileName;
    end;

    function FileOutputStream.hasOpenError(): boolean;
    begin
        result := handle = THandle(-1);
    end;

    procedure FileOutputStream.checkOpenError();
    begin
        if handle = THandle(-1) then begin
            raise FileCreationException.create(fileName, 'FileOutputStream.checkOpenError: ' + msgFileCreationError);
        end;
    end;

    procedure FileOutputStream.close();
    begin
        closeHandle(handle);
        handle := THandle(-1);
        inherited close();
    end;
{%endregion}

{%region FileIOStream }
    constructor FileIOStream.create(const fileName: AnsiString);
    begin
        create(stringToUTF16(fileName));
    end;

    constructor FileIOStream.create(const fileName: UnicodeString);
    var
        handle: THandle;
        instrm: HandleInputStream;
        outstrm: HandleOutputStream;
    begin
        inherited create();
        handle := createFileW(PWideChar(fileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, getFileAttributesW(PWideChar(fileName)), 0);
        instrm := FileIOStream0HandleInputStream.create(handle);
        outstrm := FileIOStream0HandleOutputStream.create(handle);
        self.fileName := fileName;
        self.inputPtr := instrm;
        self.inputIntf := instrm;
        self.outputPtr := outstrm;
        self.outputIntf := outstrm;
    end;

    function FileIOStream.seekSupported(): boolean;
    begin
        result := true;
    end;

    function FileIOStream.truncateSupported(): boolean;
    begin
        result := true;
    end;

    function FileIOStream.seek(delta: long): long;
    var
        outstrm: FileIOStream0HandleOutputStream;
    begin
        outstrm := FileIOStream0HandleOutputStream(outputPtr);
        outstrm.reset(outstrm.bufferedPosition + delta);
        result := outstrm.position();
        outstrm.bufferedPosition := result;
    end;

    function FileIOStream.reset(position: long): long;
    var
        outstrm: FileIOStream0HandleOutputStream;
    begin
        outstrm := FileIOStream0HandleOutputStream(outputPtr);
        outstrm.reset(position);
        result := outstrm.position();
        outstrm.bufferedPosition := result;
    end;

    function FileIOStream.truncate(): long;
    var
        instrm: FileIOStream0HandleInputStream;
        outstrm: FileIOStream0HandleOutputStream;
    begin
        instrm := FileIOStream0HandleInputStream(inputPtr);
        outstrm := FileIOStream0HandleOutputStream(outputPtr);
        outstrm.reset(outstrm.bufferedPosition);
        result := outstrm.position();
        outstrm.bufferedPosition := result;
        if instrm.bufferedPosition > result then begin
            instrm.bufferedPosition := result;
        end;
        setEndOfFile(instrm.handle);
    end;

    function FileIOStream.getInputStream(): InputStream;
    begin
        result := inputPtr;
    end;

    function FileIOStream.getOutputStream(): OutputStream;
    begin
        result := outputPtr;
    end;

    function FileIOStream.getFileName(): UnicodeString;
    begin
        result := fileName;
    end;

    function FileIOStream.hasOpenError(): boolean;
    begin
        result := inputPtr.handle = THandle(-1);
    end;

    procedure FileIOStream.checkOpenError();
    begin
        if inputPtr.handle = THandle(-1) then begin
            raise FileCreationException.create(fileName, 'FileIOStream.checkOpenError: ' + msgFileCreationError);
        end;
    end;

    procedure FileIOStream.close();
    begin
        closeHandle(inputPtr.handle);
        inputPtr.handle := THandle(-1);
        outputPtr.handle := THandle(-1);
        inherited close();
    end;
{%endregion}

{%region LocalDiskInfo }
    constructor LocalDiskInfo.create(letter: wchar);
    begin
        inherited create(ATTR_DIRECTORY, LONG_MIN_VALUE, LONG_MIN_VALUE, LONG_MIN_VALUE, 0, letter + ':');
    end;

    function LocalDiskInfo.findNext(): boolean;
    begin
        result := false;
    end;
{%endregion}

{%region LocalDiskEnumeration }
    constructor LocalDiskEnumeration.create(letter: wchar);
    begin
        inherited create(letter);
        self.letter := letter;
    end;

    function LocalDiskEnumeration.findNext(): boolean;
    var
        letter: wchar;
        availableBytes: long;
        totalFreeBytes: long;
        totalSizeInBytes: long;
        find: UnicodeString;
    begin
        for letter := wchar(int(self.letter) + 1) to 'Z' do begin
            find := letter + (':' + DIRECTORY_SEPARATOR);
            if getDiskFreeSpaceExW(PWideChar(find), @availableBytes, @totalSizeInBytes, @totalFreeBytes) then begin
                setAttributes(ATTR_DIRECTORY, LONG_MIN_VALUE, LONG_MIN_VALUE, LONG_MIN_VALUE, 0, letter + ':');
                self.letter := letter;
                result := true;
                exit;
            end;
        end;
        result := false;
    end;
{%endregion}

{%region LocalFileEnumeration }
    class function LocalFileEnumeration.getFileTime(const fileTime: TFileTime): long;
    var
        clnd: Calendar;
        time: TSystemTime;
    begin
        initialize(time);
        if not fileTimeToSystemTime(fileTime, time) then begin
            result := LONG_MIN_VALUE;
            exit;
        end;
        clnd := GregorianCalendar.create();
        clnd.offset := 0;
        clnd.year := time.year;
        clnd.month := time.month;
        clnd.day := time.day;
        clnd.hour := time.hour;
        clnd.minute := time.minute;
        clnd.second := time.second;
        clnd.millisecond := time.millisecond;
        result := clnd.time;
    end;

    class function LocalFileEnumeration.getFileSize(const findData: TWin32FindDataW): long;
    begin
        result := long(findData.nFileSizeLow) or (long(findData.nFileSizeHigh) shl 32);
    end;

    class function LocalFileEnumeration.getFileName(const findData: TWin32FindDataW): UnicodeString;
    begin
        result := UnicodeString(PWideChar(@findData.cFileName));
    end;

    class procedure LocalFileEnumeration.toFileTime(const timeInMillis: long; var fileTime: TFileTime);
    var
        clnd: Calendar;
        time: TSystemTime;
    begin
        fileTime.dwLowDateTime := 0;
        fileTime.dwHighDateTime := 0;
        clnd := GregorianCalendar.create();
        clnd.offset := 0;
        if timeInMillis < clnd.epochStart then exit;
        clnd.time := timeInMillis;
        time.year := System.UInt16(clnd.year);
        time.month := System.UInt16(clnd.month);
        time.dayOfWeek := System.UInt16(clnd.weekday);
        time.day := System.UInt16(clnd.day);
        time.hour := System.UInt16(clnd.hour);
        time.minute := System.UInt16(clnd.minute);
        time.second := System.UInt16(clnd.second);
        time.millisecond := System.UInt16(clnd.millisecond);
        systemTimeToFileTime(time, fileTime);
    end;

    constructor LocalFileEnumeration.create(handle: THandle; const findData: TWin32FindDataW);
    begin
        inherited create(int(findData.dwFileAttributes), getFileTime(findData.ftCreationTime), getFileTime(findData.ftLastWriteTime), getFileTime(findData.ftLastAccessTime), getFileSize(findData), getFileName(findData));
        self.handle := handle;
        self.findData := findData;
    end;

    function LocalFileEnumeration.findNext(): boolean;
    var
        handle: THandle;
        findData: PWin32FindDataW;
        found: UnicodeString;
    begin
        handle := self.handle;
        findData := @self.findData;
        while findNextFilew(handle, findData) do begin
            found := getFileName(findData^);
            if (found <> '.') and (found <> '..') then begin
                setAttributes(int(findData.dwFileAttributes), getFileTime(findData.ftCreationTime), getFileTime(findData.ftLastWriteTime), getFileTime(findData.ftLastAccessTime), getFileSize(findData^), found);
                result := true;
                exit;
            end;
        end;
        result := false;
    end;

    procedure LocalFileEnumeration.close();
    begin
        findClose(handle);
        inherited close();
    end;
{%endregion}

{%region PositionHandleInputStream }
    function FileIOStream0HandleInputStream.seek(delta: long): long;
    begin
        inherited reset(bufferedPosition);
        result := inherited seek(delta);
        bufferedPosition := result;
    end;

    function FileIOStream0HandleInputStream.reset(position: long): long;
    begin
        result := inherited reset(position);
        bufferedPosition := result;
    end;

    function FileIOStream0HandleInputStream.position(): long;
    begin
        result := bufferedPosition;
    end;

    function FileIOStream0HandleInputStream.available(): long;
    begin
        inherited reset(bufferedPosition);
        result := inherited available();
    end;

    function FileIOStream0HandleInputStream.read(): int;
    begin
        inherited reset(bufferedPosition);
        result := inherited read();
        bufferedPosition := inherited position();
    end;

    function FileIOStream0HandleInputStream.read(const dst: byte_Array1d; offset, length: int): int;
    begin
        inherited reset(bufferedPosition);
        result := inherited read(dst, offset, length);
        bufferedPosition := inherited position();
    end;
{%endregion}

{%region PositionHandleOutputStream }
    function FileIOStream0HandleOutputStream.position(): long;
    var
        handle: THandle;
        poshi: int;
        poslo: int;
    begin
        handle := self.handle;
        if handle = THandle(-1) then begin
            raise IOException.create('HandleOutputStream: ' + msgFileWriteError);
        end;
        poshi := 0;
        poslo := setFilePointer(handle, 0, @poshi, FILE_CURRENT);
        result := (long(poshi) shl 32) or (long(poslo) and $00000000ffffffff);
    end;

    procedure FileIOStream0HandleOutputStream.reset(position: long);
    var
        handle: THandle;
        poshi: int;
    begin
        handle := self.handle;
        if handle = THandle(-1) then begin
            raise IOException.create('HandleOutputStream: ' + msgFileWriteError);
        end;
        poshi := int(position shr 32);
        setFilePointer(handle, int(position), @poshi, FILE_BEGIN);
    end;

    procedure FileIOStream0HandleOutputStream.write(data: int);
    begin
        reset(bufferedPosition);
        inherited write(data);
        bufferedPosition := position();
    end;

    procedure FileIOStream0HandleOutputStream.write(const src: byte_Array1d; offset, length: int);
    begin
        reset(bufferedPosition);
        inherited write(src, offset, length);
        bufferedPosition := position();
    end;
{%endregion}

initialization
    LocalFileSystem.instance := LocalFileSystem.create();
    LocalFileSystem.instance.allowDestroy := false;

    classInfoAdd([
        typeInfo(MemoryRegion)
    ]);

finalization
    LocalFileSystem.instance.allowDestroy := true;
    LocalFileSystem.instance.free();

end.