diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a0367055e..30bab60159 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -373,9 +373,6 @@ catalogs: draggabilly: specifier: ^2.2.0 version: 2.4.1 - drivelist: - specifier: git+https://github.com/TanninOne/drivelist#720d1890db11482ec05fc0f6aa176cfa6e6844dd - version: 10.0.2 electron: specifier: 42.0.0 version: 42.0.0 @@ -4038,9 +4035,6 @@ importers: draggabilly: specifier: 'catalog:' version: 2.4.1 - drivelist: - specifier: 'catalog:' - version: https://codeload.github.com/TanninOne/drivelist/tar.gz/720d1890db11482ec05fc0f6aa176cfa6e6844dd electron-context-menu: specifier: 'catalog:' version: 3.6.1 @@ -4676,9 +4670,6 @@ importers: draggabilly: specifier: 'catalog:' version: 2.4.1 - drivelist: - specifier: 'catalog:' - version: https://codeload.github.com/TanninOne/drivelist/tar.gz/720d1890db11482ec05fc0f6aa176cfa6e6844dd electron: specifier: 'catalog:' version: 42.0.0 @@ -4748,6 +4739,9 @@ importers: jsonwebtoken: specifier: 'catalog:' version: 9.0.3 + koffi: + specifier: ^2.9.0 + version: 2.16.1 lodash: specifier: 'catalog:' version: 4.17.23 @@ -7933,9 +7927,6 @@ packages: big.js@5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - birecord@0.1.1: resolution: {integrity: sha512-VUpsf/qykW0heRlC8LooCq28Kxn3mAqKohhDG/49rrsQ1dT1CXyj/pgXS+5BSRzFTR/3DyIBOqQOrGyZOh71Aw==} @@ -8677,11 +8668,6 @@ packages: draggabilly@2.4.1: resolution: {integrity: sha512-HHHLPEPZqRXIDQDFRFdK7RONZausNlJ4WkA73ST7Z6O2HPWttxFHVwHo8nccuDLzXWwiVKRVuc6fTkW+CQA++A==} - drivelist@https://codeload.github.com/TanninOne/drivelist/tar.gz/720d1890db11482ec05fc0f6aa176cfa6e6844dd: - resolution: {tarball: https://codeload.github.com/TanninOne/drivelist/tar.gz/720d1890db11482ec05fc0f6aa176cfa6e6844dd} - version: 10.0.2 - engines: {node: '>=10'} - dts-resolver@2.1.3: resolution: {integrity: sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==} engines: {node: '>=20.19.0'} @@ -9194,9 +9180,6 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - filelist@1.0.6: resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} @@ -10095,6 +10078,9 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} + koffi@2.16.1: + resolution: {integrity: sha512-0Ie6CfD026dNfWSosDw9dPxPzO9Rlyo0N8m5r05S8YjytIpuilzMFDMY4IDy/8xQsTwpuVinhncD+S8n3bcYZQ==} + layout-base@1.0.2: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} @@ -16094,10 +16080,6 @@ snapshots: big.js@5.2.2: {} - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - birecord@0.1.1: {} birpc@4.0.0: {} @@ -17001,15 +16983,6 @@ snapshots: get-size: 2.0.3 unidragger: 2.4.0 - drivelist@https://codeload.github.com/TanninOne/drivelist/tar.gz/720d1890db11482ec05fc0f6aa176cfa6e6844dd: - dependencies: - bindings: 1.5.0 - debug: 3.2.7 - node-addon-api: 8.5.0 - prebuild-install: 7.1.0 - transitivePeerDependencies: - - supports-color - dts-resolver@2.1.3: {} dunder-proto@1.0.1: @@ -17782,8 +17755,6 @@ snapshots: dependencies: flat-cache: 4.0.1 - file-uri-to-path@1.0.0: {} - filelist@1.0.6: dependencies: minimatch: 5.1.9 @@ -18764,6 +18735,8 @@ snapshots: kind-of@6.0.3: {} + koffi@2.16.1: {} + layout-base@1.0.2: {} lazy-val@1.0.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8d69814426..d21b6759b3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -16,9 +16,9 @@ allowBuilds: bsatk: true core-js: true crash-dump: true - drivelist: true electron: true esbuild: true + koffi: true font-scanner: true gamebryo-savegame: true leveldown: true @@ -153,7 +153,6 @@ catalog: dequal: ^2.0.3 dnd-core: ^9.4.0 draggabilly: ^2.2.0 - drivelist: git+https://github.com/TanninOne/drivelist#720d1890db11482ec05fc0f6aa176cfa6e6844dd electron: 42.0.0 electron-builder: 24.13.3 electron-context-menu: ^3.1.1 diff --git a/src/main/package.json b/src/main/package.json index d8f30f571a..7d8839082a 100644 --- a/src/main/package.json +++ b/src/main/package.json @@ -54,7 +54,6 @@ "dequal": "catalog:", "dnd-core": "catalog:", "draggabilly": "catalog:", - "drivelist": "catalog:", "electron-context-menu": "catalog:", "electron-redux": "catalog:", "electron-updater": "catalog:", diff --git a/src/renderer/package.json b/src/renderer/package.json index aba8f7a037..45cf4e5176 100644 --- a/src/renderer/package.json +++ b/src/renderer/package.json @@ -97,7 +97,6 @@ "dequal": "catalog:", "dnd-core": "catalog:", "draggabilly": "catalog:", - "drivelist": "catalog:", "electron": "catalog:", "electron-context-menu": "catalog:", "electron-redux": "catalog:", @@ -120,6 +119,7 @@ "immutability-helper": "catalog:", "interweave": "catalog:", "is-admin": "catalog:", + "koffi": "^2.9.0", "json-loader": "catalog:", "json-socket": "catalog:", "jsonwebtoken": "catalog:", diff --git a/src/renderer/src/extensions/gamemode_management/util/getDriveList.ts b/src/renderer/src/extensions/gamemode_management/util/getDriveList.ts index 566750bd1b..3dbf1dcf6a 100644 --- a/src/renderer/src/extensions/gamemode_management/util/getDriveList.ts +++ b/src/renderer/src/extensions/gamemode_management/util/getDriveList.ts @@ -1,51 +1,104 @@ -import type { list as listT } from "drivelist"; +import { readFile } from "fs/promises"; import type { IExtensionApi } from "../../../types/IExtensionContext"; +/** + * Returns mount paths for fixed (non-removable) system drives. + * Uses koffi FFI to call Win32 APIs on Windows, /proc/mounts on Linux. + */ function getDriveList(api: IExtensionApi): Promise { - let list: typeof listT; - try { - list = require("drivelist").list; - if (typeof list !== "function") { - throw new Error('Failed to load "drivelist" module'); - } - } catch (err) { + const impl = process.platform === "win32" ? getFixedDrivesWindows : getFixedDrivesLinux; + + return impl().catch((err) => { api.showErrorNotification( - "Failed to query list of system drives", - { - message: - "Vortex was not able to query the operating system for the list of system drives. " + - "If this error persists, please configure the list manually.", - error: err, - }, + "Failed to determine list of disk drives. " + + "Please review the settings before scanning for games.", + err, { allowReport: false }, ); + return ["C:"]; + }); +} + +/** + * Enumerate fixed drives via Win32 GetLogicalDriveStringsW + GetDriveTypeW. + * + * GetLogicalDriveStringsW returns a double-null-terminated buffer of drive + * root paths ("C:\\\0D:\\\0\0"). GetDriveTypeW returns the type for each; + * DRIVE_FIXED (3) is what we want. + */ +async function getFixedDrivesWindows(): Promise { + const koffi = await import("koffi"); + const kernel32 = koffi.load("kernel32.dll"); + + const GetLogicalDriveStringsW = kernel32.func( + "uint32 GetLogicalDriveStringsW(uint32 nBufferLength, uint16 *lpBuffer)", + ); + const GetDriveTypeW = kernel32.func("uint32 GetDriveTypeW(const uint16 *lpRootPathName)"); + + const DRIVE_FIXED = 3; + + // Buffer for drive strings — 26 drives * 4 chars ("X:\\\0") = 104 UTF-16 code units max + const buf = Buffer.alloc(256); + const len = GetLogicalDriveStringsW(buf.length / 2, buf) as number; + + if (len === 0) { return Promise.resolve(["C:"]); } - return list() - .then((disks) => - disks - .sort() - .filter((disk) => disk.isSystem && !disk.isRemovable) - .reduce((prev, disk) => { - if (disk.mountpoints) { - prev.push(...disk.mountpoints.map((mp) => mp.path)); - } else if (disk["mountpoint"] !== undefined) { - prev.push(disk["mountpoint"]); - } - return prev; - }, []), + const drives: string[] = []; + let offset = 0; + + while (offset < len * 2) { + // Read null-terminated UTF-16LE string + const codes: number[] = []; + while (offset < buf.length) { + const code = buf.readUInt16LE(offset); + offset += 2; + if (code === 0) break; + codes.push(code); + } + if (codes.length === 0) break; + + const rootPath = String.fromCharCode(...codes); // e.g. "C:\\" + + // Check drive type + const widePath = Buffer.alloc((rootPath.length + 1) * 2); + for (let i = 0; i < rootPath.length; i++) { + widePath.writeUInt16LE(rootPath.charCodeAt(i), i * 2); + } + + const driveType = GetDriveTypeW(widePath) as number; + if (driveType === DRIVE_FIXED) { + // Strip trailing backslash: "C:\\" → "C:" + drives.push(rootPath.replace(/\\$/, "")); + } + } + + return Promise.resolve(drives.length > 0 ? drives : ["C:"]); +} + +async function getFixedDrivesLinux(): Promise { + const raw = await readFile("/proc/mounts", "utf8"); + const drives = raw + .split("\n") + .filter((line) => line.length > 0) + .map((line) => { + const parts = line.split(" "); + return { device: parts[0], mountpoint: parts[1], fstype: parts[2] }; + }) + .filter( + ({ device, fstype }) => + device.startsWith("/dev/") && + !device.startsWith("/dev/loop") && + ["ext4", "btrfs", "xfs", "zfs", "ext3", "ext2", "f2fs"].includes(fstype), ) - .catch((err) => { - api.showErrorNotification( - "Failed to determine list of disk drives. " + - "Please review the settings before scanning for games.", - err, - { allowReport: false }, - ); - return ["C:"]; - }); + .map(({ mountpoint }) => + // Unescape octal sequences in mount paths (e.g. \040 for space) + mountpoint.replace(/\\([0-7]{3})/g, (_, oct) => String.fromCharCode(parseInt(oct, 8))), + ); + + return drives.length > 0 ? drives : ["/"]; } export default getDriveList;