unit VfsControl; (* Facade unit for high-level VFS API. *) (***) interface (***) uses Windows, SysUtils, Utils, WinUtils, TypeWrappers, DataLib, Files, StrLib, VfsBase, VfsUtils, VfsHooks, DlgMes {FIXME DELETEME}; type (* Import *) TWideString = TypeWrappers.TWideString; (* Runs all VFS subsystems, unless VFS is already running *) function RunVfs (DirListingOrder: VfsBase.TDirListingSortType): LONGBOOL; stdcall; (* Loads mod list from file and maps each mod directory to specified root directory. File with mod list is treated as (BOM or BOM-less) UTF-8 plain text file, where each mod name is separated from another one via Line Feed (#10) character. Each mod named is trimmed, converted to UCS16 and validated before adding to list. Invalid or empty mods will be skipped. Returns true if root and mods directory existed and file with mod list was loaded successfully *) function MapModsFromList (const RootDir, ModsDir, ModListFile: WideString; Flags: integer = 0): boolean; (***) implementation (***) type TModList = DataLib.TList {of (O) TWideString}; function RunVfs (DirListingOrder: VfsBase.TDirListingSortType): LONGBOOL; stdcall; var CurrDir: WideString; begin with VfsBase.VfsCritSection do begin Enter; result := VfsBase.RunVfs(DirListingOrder); if result then begin VfsHooks.InstallHooks; // Try to ensure, that current directory handle is tracked by VfsOpenFiles CurrDir := WinUtils.GetCurrentDirW; if CurrDir <> '' then begin WinUtils.SetCurrentDirW(CurrDir); end; end; Leave; end; // .with end; // function RunVfs function ValidateModName (const ModName: WideString): boolean; const DISALLOWED_CHARS = ['<', '>', '"', '?', '*', '\', '/', '|', ':', #0]; var StrLen: integer; i: integer; begin StrLen := Length(ModName); i := 1; while (i <= StrLen) and ((ord(ModName[i]) > 255) or not (AnsiChar(ModName[i]) in DISALLOWED_CHARS)) do begin Inc(i); end; result := (i > StrLen) and (ModName <> '') and (ModName <> '.') and (ModName <> '..'); end; function LoadModList (const ModListFilePath: WideString; {O} var {out} ModList: TModList): boolean; const UTF8_BOM = #$EF#$BB#$BF; var {O} OpenedFile: Files.TFile; AbsFilePath: WideString; FileHandle: integer; FileContents: string; Lines: Utils.TArrayOfStr; ModNameUtf8: string; ModName: WideString; i: integer; begin OpenedFile := Files.TFile.Create; // * * * * * // AbsFilePath := VfsUtils.NormalizePath(ModListFilePath); FileHandle := integer(Windows.INVALID_HANDLE_VALUE); result := AbsFilePath <> ''; if result then begin FileHandle := Windows.CreateFileW(PWideChar(AbsFilePath), Windows.GENERIC_READ, Windows.FILE_SHARE_READ, nil, Windows.OPEN_EXISTING, 0, 0); result := FileHandle <> integer(Windows.INVALID_HANDLE_VALUE); end; if result then begin result := Files.ReadFileContents(FileHandle, FileContents); if result then begin SysUtils.FreeAndNil(ModList); ModList := DataLib.NewList(Utils.OWNS_ITEMS); if (Length(FileContents) >= 3) and (FileContents[1] = UTF8_BOM[1]) and (FileContents[2] = UTF8_BOM[2]) and (FileContents[3] = UTF8_BOM[3]) then begin FileContents := Copy(FileContents, Length(UTF8_BOM) + 1); end; Lines := StrLib.Explode(FileContents, #10); for i := 0 to High(Lines) do begin ModNameUtf8 := Lines[i]; ModName := StrLib.TrimW(StrLib.Utf8ToWide(ModNameUtf8, StrLib.FAIL_ON_ERROR)); if ValidateModName(ModName) then begin ModList.Add(TWideString.Create(ModName)); end; end; end; Windows.CloseHandle(FileHandle); end; // .if // * * * * * // SysUtils.FreeAndNil(OpenedFile); end; // .function LoadModList function MapModsFromList_ (const RootDir, ModsDir: WideString; ModList: TModList; Flags: integer = 0): boolean; var AbsRootDir: WideString; AbsModsDir: WideString; FileInfo: VfsUtils.TNativeFileInfo; ModName: WideString; ModPathPrefix: WideString; i: integer; begin {!} Assert(ModList <> nil); // * * * * * // AbsRootDir := VfsUtils.NormalizePath(RootDir); AbsModsDir := VfsUtils.NormalizePath(ModsDir); result := (AbsRootDir <> '') and (AbsModsDir <> '') and VfsUtils.GetFileInfo(AbsRootDir, FileInfo) and Utils.HasFlag(Windows.FILE_ATTRIBUTE_DIRECTORY, FileInfo.Base.FileAttributes) and VfsUtils.GetFileInfo(AbsModsDir, FileInfo) and Utils.HasFlag(Windows.FILE_ATTRIBUTE_DIRECTORY, FileInfo.Base.FileAttributes); if result then begin ModPathPrefix := VfsUtils.AddBackslash(AbsModsDir); for i := ModList.Count - 1 downto 0 do begin ModName := TWideString(ModList[i]).Value; VfsBase.MapDir(AbsRootDir, ModPathPrefix + ModName, not VfsBase.OVERWRITE_EXISTING, Flags); end; end; // .if end; // .function MapModsFromList function MapModsFromList (const RootDir, ModsDir, ModListFile: WideString; Flags: integer = 0): boolean; var {O} ModList: TModList; begin ModList := nil; // * * * * * // result := VfsBase.EnterVfsConfig; if result then begin try result := LoadModList(ModListFile, ModList) and MapModsFromList_(RootDir, ModsDir, ModList, Flags); finally VfsBase.LeaveVfsConfig; end; end; // * * * * * // SysUtils.FreeAndNil(ModList); end; // .function MapModsFromList end.