// Beweis-Test: Kann Node/koffi dieselben Win32-Aufrufe wie Rippys Python-Treiber? // NUR LESEND — kein Auswerfen, kein Verriegeln. const koffi = require('koffi'); // ── CTL_CODE aus winioctl.h, eins zu eins wie in src/rippy/drives/win_ioctl.py const ctl = (typ, fn, methode, zugriff) => ((typ << 16) | (zugriff << 14) | (fn << 2) | methode) >>> 0; const FILE_DEVICE_CD_ROM = 0x02, FILE_DEVICE_DISK = 0x07, FILE_DEVICE_MASS_STORAGE = 0x2d; const IOCTL_STORAGE_CHECK_VERIFY2 = ctl(FILE_DEVICE_MASS_STORAGE, 0x0200, 0, 0); const IOCTL_STORAGE_QUERY_PROPERTY = ctl(FILE_DEVICE_MASS_STORAGE, 0x0500, 0, 0); const IOCTL_CDROM_DISK_TYPE = ctl(FILE_DEVICE_CD_ROM, 0x0010, 0, 0); const IOCTL_DISK_GET_LENGTH_INFO = ctl(FILE_DEVICE_DISK, 0x0017, 0, 1); 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()'); 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)'); const GENERIC_READ = 0x80000000, FILE_SHARE_READ = 1, FILE_SHARE_WRITE = 2, OPEN_EXISTING = 3; const DRIVE_CDROM = 5; // ── 1. Laufwerke finden (die Windows-Entsprechung von glob /dev/sr*) const maske = GetLogicalDrives(); const optische = []; for (let i = 0; i < 26; i++) { if (!(maske & (1 << i))) continue; const buchstabe = String.fromCharCode(65 + i); if (GetDriveTypeW(buchstabe + ':\\') === DRIVE_CDROM) optische.push(buchstabe); } console.log('1. GetLogicalDrives + GetDriveTypeW -> optische Laufwerke:', optische); if (!optische.length) { console.log(' Kein optisches Laufwerk — Test endet hier.'); process.exit(0); } const lw = optische[0]; // ── 2. Gerät öffnen. Zugriff 0 = nur das GERÄT fragen, nicht das Medium. // Genau die Unterscheidung aus SAVEPOINT rc11: Bei gestörtem Medium // scheitert GENERIC_READ, Zugriff 0 geht weiter. function oeffnen(zugriff) { const h = CreateFileW('\\\\.\\' + lw + ':', zugriff, FILE_SHARE_READ | FILE_SHARE_WRITE, null, OPEN_EXISTING, 0, null); const adr = koffi.address(h); // INVALID_HANDLE_VALUE ist -1, als vorzeichenlose 64-Bit-Zahl 0xFFFF... if (adr === 0n || adr === 0xffffffffffffffffn) return { h: null, fehler: GetLastError() }; return { h, fehler: 0 }; } const ohneMedium = oeffnen(0); console.log(`2. CreateFileW \\\\.\\${lw}: (Zugriff 0) -> ` + (ohneMedium.h ? 'offen' : 'Win32-Fehler ' + ohneMedium.fehler)); const mitLesen = oeffnen(GENERIC_READ); console.log(` CreateFileW \\\\.\\${lw}: (GENERIC_READ) -> ` + (mitLesen.h ? 'offen' : 'Win32-Fehler ' + mitLesen.fehler)); const h = ohneMedium.h; if (!h) { console.log(' Gerät nicht ansprechbar — Test endet hier.'); process.exit(0); } const rueck = Buffer.alloc(4); // ── 3. Hersteller/Modell/Serial (STORAGE_DEVICE_DESCRIPTOR) const anfrage = Buffer.alloc(12); anfrage.writeUInt32LE(0, 0); // PropertyId = StorageDeviceProperty anfrage.writeUInt32LE(0, 4); // QueryType = PropertyStandardQuery const antwort = Buffer.alloc(1024); if (DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY, anfrage, 12, antwort, 1024, rueck, null)) { const text = (off) => { const start = antwort.readUInt32LE(off); if (!start || start >= antwort.length) return ''; let ende = start; while (ende < antwort.length && antwort[ende] !== 0) ende++; return antwort.toString('latin1', start, ende).trim(); }; console.log('3. IOCTL_STORAGE_QUERY_PROPERTY -> Hersteller:', text(12), '| Modell:', text(16), '| Rev:', text(20), '| Serial:', text(24)); } else { console.log('3. IOCTL_STORAGE_QUERY_PROPERTY -> Win32-Fehler', GetLastError()); } // ── 4. Liegt ein Medium drin? (ERROR_NOT_READY = 21 heißt: leer) const okVerify = DeviceIoControl(h, IOCTL_STORAGE_CHECK_VERIFY2, null, 0, null, 0, rueck, null); console.log('4. IOCTL_STORAGE_CHECK_VERIFY2 -> ' + (okVerify ? 'Medium eingelegt' : 'kein Medium (Win32-Fehler ' + GetLastError() + ')')); // ── 5. Audio- oder Datenspur? const typBuf = Buffer.alloc(1); if (DeviceIoControl(h, IOCTL_CDROM_DISK_TYPE, null, 0, typBuf, 1, rueck, null)) { const t = typBuf[0]; console.log('5. IOCTL_CDROM_DISK_TYPE -> ' + (t === 1 ? 'Audio-CD' : t === 2 ? 'Daten-Disc' : 'Wert ' + t)); } else { console.log('5. IOCTL_CDROM_DISK_TYPE -> Win32-Fehler', GetLastError()); } // ── 6. Größe des Mediums (für die Unterscheidung DVD / BD / UHD) const laenge = Buffer.alloc(8); if (mitLesen.h && DeviceIoControl(mitLesen.h, IOCTL_DISK_GET_LENGTH_INFO, null, 0, laenge, 8, rueck, null)) { const bytes = laenge.readBigUInt64LE(0); const gb = Number(bytes) / 1e9; console.log('6. IOCTL_DISK_GET_LENGTH_INFO -> ' + bytes + ' Bytes (' + gb.toFixed(2) + ' GB)' + ' => Einordnung: ' + (gb > 60 ? 'UHD' : gb > 9.5 ? 'Blu-ray' : gb > 1 ? 'DVD' : 'CD')); } else { console.log('6. IOCTL_DISK_GET_LENGTH_INFO -> Win32-Fehler', GetLastError()); } CloseHandle(h); if (mitLesen.h) CloseHandle(mitLesen.h); console.log('\nAlle Handles geschlossen. Nichts ausgeworfen, nichts verriegelt.');