Die heutige Windows-Fassung ist der Docker-Code in einer EXE: PyInstaller
buendelt docker/api und docker/worker, uvicorn laeuft auf 127.0.0.1:7788,
pywebview stellt ein Fenster davor. Elf Release-Kandidaten in drei Tagen,
und die Funde sind fast alle derselbe Typ — Linux-Annahmen, die unter
Windows etwas anderes bedeuten (timeout ls -d, shutil.which, /app,
/dev/{name}, F: ohne Trenner).
Das Konzept beschreibt den Neubau als Windows-Programm: Electron 44,
TypeScript durchgehend, Einzelplatz, kein Python, kein HTTP-Server.
Vier Entscheide des Commanders (30.08.2026) sind eingearbeitet:
alles TypeScript/Node · Docker bleibt unangetastet · reiner Einzelplatz ·
eigener Branch.
Vor der ersten Zeile Konzept gemessen (Regel D), Belege in beweise/:
* Win32 aus Node an G: (HL-DT-ST BD-RE BU40N) — Laufwerkssuche,
CreateFileW, QUERY_PROPERTY (Modell + Serial), CHECK_VERIFY2,
GET_LENGTH_INFO (33,76 GB -> Blu-ray). IOCTL_CDROM_DISK_TYPE
antwortet mit Fehler 50 — derselbe Befund wie im Python-Treiber.
* Prozess-Leine (Job Object): Kind stirbt mit dem per taskkill /F
ohne /T abgeschossenen Elternprozess. Die Messung aus rc10, in
Node nachgestellt.
* node:sqlite ist in Node 24 eingebaut — in Electron noch ungeprueft,
ausdruecklich als offener Punkt markiert.
Akutestes Risiko im Dokument: MakeMKVs freier Beta-Key laeuft Ende
September 2026 ab, die Kaufseite fuer die Dauerlizenz ist seit Mai defekt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
110 lines
5.6 KiB
JavaScript
110 lines
5.6 KiB
JavaScript
// 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.');
|