WAS: win32.ts kennt QueryDosDeviceW (am 12.09.2026 an diesem PC gemessen: C: → \Device\HarddiskVolume7, X: → \Device\LanmanRedirector\…\192.168.178.62\rippy); LaufwerkApi.geraeteName liefert das Kernel-Gerät hinter dem Buchstaben. beurteilen() filtert damit auf DIESES Gerät; ohne Gerätename zählt nur noch der cdrom-Treiber (gehoertZuLaufwerk). Vorher nahm der XPath cdrom UND disk für alle Geräte — eine hustende Festplatte oder ein zweites Laufwerk erzeugte „VERDACHT: DIE HARDWARE" in der Kachel des Blu-ray- Laufwerks. WARUM: Durchsicht 12.09.2026, Fund F11 — mit Tests (Festplatten- Ereignisse, zweites Laufwerk, ruhig mit Gerätename). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
108 lines
4.4 KiB
TypeScript
108 lines
4.4 KiB
TypeScript
// DER EINZIGE ORT MIT koffi im Kern (Regel R1, KONZEPT § 4.3) — die rohe
|
|
// Win32-Anbindung für optische Laufwerke. Entscheidungen stehen NICHT hier,
|
|
// sondern in disc.ts; die Konstanten samt Herleitung in codes.ts. Dadurch
|
|
// ist alles außer diesen Aufrufen ohne Windows und ohne Laufwerk prüfbar
|
|
// (dasselbe Muster wie win_ioctl.py/windows.py im Docker-Zweig, dort an
|
|
// echter Hardware bewiesen — 28.08.2026, BU40N; hier gemessen in
|
|
// beweise/laufwerk.js, 30.08.2026).
|
|
import koffi from 'koffi'
|
|
import { FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, Win32Fehler, type LaufwerkApi } from './codes'
|
|
|
|
export type { LaufwerkApi }
|
|
|
|
let echteApi: LaufwerkApi | null = null
|
|
|
|
/** Die echten kernel32-Aufrufe — einmal geladen, dann wiederverwendet. */
|
|
export function win32Api(): LaufwerkApi {
|
|
if (echteApi !== null) return echteApi
|
|
|
|
const kernel32 = koffi.load('kernel32.dll')
|
|
const GetLogicalDrives = kernel32.func('uint32_t __stdcall GetLogicalDrives()')
|
|
const GetDriveTypeW = kernel32.func('uint32_t __stdcall GetDriveTypeW(str16 lpRootPathName)')
|
|
const CreateFileW = kernel32.func(
|
|
'void* __stdcall CreateFileW(str16 lpFileName, uint32_t dwDesiredAccess, uint32_t dwShareMode, void *lpSecurityAttributes, uint32_t dwCreationDisposition, uint32_t dwFlagsAndAttributes, void *hTemplateFile)',
|
|
)
|
|
const CloseHandle = kernel32.func('bool __stdcall CloseHandle(void *hObject)')
|
|
const GetLastError = kernel32.func('uint32_t __stdcall GetLastError()')
|
|
// 5.7.0 (Durchsicht F11): Welches Kernel-Gerät steckt hinter G:? —
|
|
// QueryDosDeviceW liefert \Device\CdRom0 (learn.microsoft.com,
|
|
// fileapi/nf-fileapi-querydosdevicew: Ziel ist eine Mehrfach-Zeichenkette,
|
|
// der erste Eintrag zählt; am 12.09.2026 an C: dieses PCs gemessen).
|
|
// Damit zählt die Laufwerks-Gesundheit nur noch die Ereignisse DIESES
|
|
// Laufwerks, nicht die einer hustenden Festplatte.
|
|
const QueryDosDeviceW = kernel32.func(
|
|
'uint32_t __stdcall QueryDosDeviceW(str16 lpDeviceName, _Out_ char16_t *lpTargetPath, uint32_t ucchMax)',
|
|
)
|
|
const DeviceIoControl = kernel32.func(
|
|
'bool __stdcall DeviceIoControl(void *hDevice, uint32_t dwIoControlCode, void *lpInBuffer, uint32_t nInBufferSize, _Out_ void *lpOutBuffer, uint32_t nOutBufferSize, _Out_ uint32_t *lpBytesReturned, void *lpOverlapped)',
|
|
)
|
|
|
|
echteApi = {
|
|
laufwerksbuchstaben(): string[] {
|
|
const maske = GetLogicalDrives() as number
|
|
const buchstaben: string[] = []
|
|
for (let i = 0; i < 26; i++) {
|
|
if (maske & (1 << i)) buchstaben.push(String.fromCharCode(65 + i))
|
|
}
|
|
return buchstaben
|
|
},
|
|
|
|
laufwerkstyp(buchstabe: string): number {
|
|
return GetDriveTypeW(`${buchstabe}:\\`) as number
|
|
},
|
|
|
|
oeffnen(geraetepfad: string, zugriff: number): unknown {
|
|
const handle = CreateFileW(
|
|
geraetepfad,
|
|
zugriff,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
null,
|
|
OPEN_EXISTING,
|
|
0,
|
|
null,
|
|
)
|
|
const adresse = koffi.address(handle as Parameters<typeof koffi.address>[0])
|
|
// INVALID_HANDLE_VALUE ist (HANDLE)-1 — als vorzeichenlose 64-Bit-Zahl
|
|
// 0xFFFF…FF, nicht -1 (dieselbe Falle wie in windows.py dokumentiert).
|
|
if (adresse === 0n || adresse === 0xffff_ffff_ffff_ffffn) {
|
|
throw new Win32Fehler('CreateFileW', GetLastError() as number)
|
|
}
|
|
return handle
|
|
},
|
|
|
|
schliessen(handle: unknown): void {
|
|
CloseHandle(handle)
|
|
},
|
|
|
|
geraeteName(buchstabe: string): string {
|
|
const ziel = Buffer.alloc(2 * 1024)
|
|
const laenge = QueryDosDeviceW(`${buchstabe.replace(/:$/, '')}:`, ziel, 1024) as number
|
|
if (laenge === 0) return ''
|
|
// UTF-16LE, die erste Zeichenkette bis zum ersten NUL.
|
|
const text = ziel.toString('utf16le', 0, laenge * 2)
|
|
const ende = text.indexOf('\0')
|
|
return (ende >= 0 ? text.slice(0, ende) : text).trim()
|
|
},
|
|
|
|
steuern(handle: unknown, code: number, eingabe?: Buffer | null, ausgabeBytes = 0): Buffer {
|
|
const aus = ausgabeBytes > 0 ? Buffer.alloc(ausgabeBytes) : null
|
|
const zurueck = [0]
|
|
const erfolg = DeviceIoControl(
|
|
handle,
|
|
code,
|
|
eingabe ?? null,
|
|
eingabe?.length ?? 0,
|
|
aus,
|
|
ausgabeBytes,
|
|
zurueck,
|
|
null,
|
|
)
|
|
if (!erfolg) {
|
|
throw new Win32Fehler(`DeviceIoControl(0x${code.toString(16)})`, GetLastError() as number)
|
|
}
|
|
return aus === null ? Buffer.alloc(0) : aus.subarray(0, zurueck[0])
|
|
},
|
|
}
|
|
return echteApi
|
|
}
|