feat(v5): Etappe W-1 — Laufwerk: Wache, Typ, Auswerfen mit Nachsehen
Ampel / ampel (push) Successful in 1m21s
Ampel / ampel (push) Successful in 1m21s
kern/laufwerk in drei Schichten: codes.ts (Steuercodes HERGELEITET wie win_ioctl.py, gegen die Doku-Zahlen getestet), win32.ts (der einzige koffi-Ort im Kern, Muster aus beweise/laufwerk.js), disc.ts (Logik gegen die LaufwerkApi-Schnittstelle — classify mit den cdrom.py-Schwellen, Geraete-Info mit ready/empty/unknown + Klartext-Grund, Auswurf als entriegeln→auswerfen→NACHSEHEN mit injizierbarem Warten). Dazu wache.ts im 3-Sekunden-Takt (§ 5 Plan A): Uebergangslogik pur, gescheiterte Runde behaelt den Stand (R2) und meldet laut (R4). Fenster zeigt Laufwerke live (Modell, Typ, Groesse, Grund) mit Auswerfen-Knopf; Ereigniszeilen fuer Disc rein/raus und Fehler. Bündel-Falle gefunden und behoben: Ein woertliches require überlebt Rollup nicht — win32 wird per dynamischem import() als eigener Chunk gebaut; der Fehler war dank R4 im Fenster sichtbar statt still. Gemessen am echten BU40N (30.08.2026, RIPPY_MESSUNG=1): Laufwerk G: status=ready typ=bluray groesse=33759690752 modell=HL-DT-ST BD-RE BU40N 1.03 serial=0025114C0149 Der Auswurf-Beweis (messung.auswurf) folgt am Ende der Sitzung — der BU40N kann die Schublade nicht selbst einziehen. 52 Tests gruen (Steuercodes, classify, Auswurf-Ablauf, Geraete-Info, Wache, Smoke weiterhin gruen mit Laufwerks-Kachel im Bild). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
6ed8035dcc
commit
a45069af5e
@@ -0,0 +1,88 @@
|
||||
// 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()')
|
||||
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)
|
||||
},
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user