Source file: /~heha/hs/lptdac.zip/LPTDAC.ASM

;**********************************************************
;***  LPTDAC-VxD, emuliert eine SoundBlaster und stellt	***
;***  eine einfache generelle API zur Verfügung.	***
;***  Demonstriert die direkte Anzapfung des Timer-	***
;***  Interrupts durch Patchen in der IDT von Windows.	***
;***  Und das funktioniert sogar!!			***
;**********************************************************
;***  haftmann#software, 26.12.1995			***
;**********************************************************
;Bugfix 01/02: Uhr-Problem ab Win95 (ist eigentlich Bug von Windows)
;Bugfix 02/97: xor edi,edi-->xor edx,edx (Divisionsüberlauf im Kernel) (h#s)
;Änderung: vmm.inc-->tvmm.inc, Standardmakros und "short" entfallen
;CallbackPrevTick und SimulatedBufSize reduziert (16-->3) für schnelle Pentiums

	.386p
	.xlist
	include	tvmm.inc
	include	vtd.inc
	include	vpicd.inc
	include	vdmad.inc
	include	shell.inc
	include	debug.inc
;	include	vxdutils.inc
;=============================
;Debug_Halt macro
; ifdef DEBUG
;	int	1
; endif
;endm
;=============================
UV macro abyte,anadr
	db	abyte
	dd	OFFSET32 anadr
endm
;=============================

	locals
;	nowarn icg
Create_LPTDAC_Service_Table=1	; LPTDAC service table created
;	include	LPTDAC.inc
	.list

TIMERFREQ	equ	1193180	;Hertz, Primfaktoren: 2*2*5*59659
TICKSPERDAY	equ	1573040	;oder 1800B0h, Primfaktoren:2*2*2*2*5*7*53*53
TIMERINT	equ	50h	;statt 08h unter DOS
;PI rund 355/113  (Eselsbrücke: 1/ (113/355))
LPTDAC_Major_Ver	equ     1
LPTDAC_Minor_Ver	equ     0
LPTDAC_Device_ID	equ     37FBh	;ist so von Microsoft bestätigt worden


Begin_Service_Table LPTDAC
LPTDAC_Service LPTDAC_Get_Version, LOCAL
LPTDAC_Service LPTDAC_Get_Owner, LOCAL
LPTDAC_Service LPTDAC_Get_Ticks, LOCAL
End_Service_Table LPTDAC

Declare_Virtual_Device  LPTDAC, LPTDAC_Major_Ver, LPTDAC_Minor_Ver,\
			LPTDAC_Control_Proc, LPTDAC_Device_ID,\
			Undefined_Init_Order,PlayAPI,PlayAPI

;***
;Per-VM-Values (später):
;- Callback-Adresse (Flat)
;- Callback-Adresse (Segment:Offset)
;- Sample-Rate
;- Fehlermeldungs-Bits
;**********************************************************
;***  Message-Funktionsverteiler			***
;**********************************************************
VxD_Locked_Code_Seg
BeginProc LPTDAC_Control_Proc
ifdef DEBUG
	cmp	al,7
	jnc	@@nodeb
	Trace_Out	">>>System_Control Service #AL"
@@nodeb:
endif
	Control_Dispatch Sys_Critical_Init, LPTDAC_Init
	Control_Dispatch Init_Complete,OnInitComplete
ifdef DEBUG
	Control_Dispatch 3, LPTDAC_Test
endif
	Control_Dispatch VM_Not_Executeable, VMDies
	clc
	ret

EndProc LPTDAC_Control_Proc

VxD_Locked_Code_ends

;**********************************************************
;***  Daten						***
;**********************************************************
VxD_Locked_Data_Seg
;Datenbereich
;*** Zentrale und LPTDAC-spezifische Daten ***
TimerVal	dd	0	;üblicher Startwert
TimerAdd	dd	27	;44.1 kHz CD-Qualität
TimerLimit	dd	65536	;wahrscheinlich (oder auch nicht)
BufPtr		dd	?	;Momentaner Abspiel-Zeiger
BufLen		dd	0	;Keine 64KB-Schallmauer!
NextPtr		dd	?	;Nächster Abspiel-Zeiger
NextLen		dd	0	;Nächste Abspiel-Länge; Null wenn keiner folgt
OldInt08	dd	?	;naja, ist ja eigentlich INT50
OwningVM	dd	0	;der momentane Besitzer, 0=LPTDAC frei
LPTDACPorts	label	dword	;Alias
LPTDACPortLeft	dw	378h	;LPT1 bei mir
LPTDACPortRight	dw	0	;Ich habe nur ein DAC
CallbackPrevTick dd	3	;normalerweise nach Ausgabe des letzten Samples
SimulatedBufSize dd	3	;Verfrühtes Callback
EobCallback	dd	0
OldSetService	dd	?	;für gehookte Timer-Services
OldReleaseService dd	?
data32seg	dw	30h	;wahrscheinlich!

VxD_Locked_Data_Ends

;**********************************************************
;***  DIE Interruptserviceroutine			***
;**********************************************************
;NewInt08-Routine
;Diese Routine muß direkt von einem 386-Interrupt-Gate gerufen werden.
;Bei einem Trap-Gate müßte ein zusätzliches CLI eine Verschachtelung
;verhindern.
;Die "08" hat eigentlich nichts mit dieser Interrupt-Nummer zu tun,
;bei mir wird vom VPICD(?) der IRQ0 auf INT50 gemappt.
;Es ist halt noch als Gedankenstütze aus der DOS-Zeit verblieben.
;
;Preisfrage: Welche Register rettet ein Interrupt-Gate? keine, FLAGS, EFLAGS
;oder alle? (E)FLAGS! Und der Stack wird (irgendwie) noch umgeschaltet.(?)
;Und aus dem V86-Mode heraus werden noch alle Segmentregister gerettet
;und nullgesetzt.
;Wie lange dauert ein Interrupt-Gate in seiner Ausführung?


VxD_Locked_Code_Seg

BeginProc NewInt08, high_freq, no_log	;um Himmels willen keine Debugstrings!
	push	eax ecx edx esi	;schneller als ein PUSHA am 386er
	push	ds
	mov	ds,cs:[data32seg]	;Datensegment setzen
	mov	ecx,[OwningVM]
	jecxz	@@ToOldInt	;einfach "durchreichen"
	mov	esi,[BufPtr]
	mov	ecx,[BufLen]
	jecxz	@@SwapBuf
@@play:	mov	edx,[LPTDACPorts]
	cld
Pat1:	lodsb	;|lodsw wenn stereo	;FAULIG!!??
	;nop
Pat2:	;xor	ax,8080h	;Nullzentriert; 4*NOP wenn 80h-zentriert
	out	dx,al
Pat3:	;nop	;|xchg	al,ah wenn stereo
	;shr	edx,16		;High-Teil für rechten Kanal benutzen
Pat4:	;nop	;|out	dx,al wenn 2 DACs
	dec	ecx		;1 Sample weniger
	mov	[BufPtr],esi
	mov	[BufLen],ecx	;und abspeichern
	cmp	ecx,[CallbackPrevTick]
	jz	@@maycallback
@@nocallback:
	mov	eax,[TimerVal]
	add	eax,[TimerAdd]
	cmp	eax,[TimerLimit]
	jnc	@@overlimit	;möglichst selten springen!!
	mov	[TimerVal],eax
	;(Alles muß man selber machen!!)
	mov	al,60h
	out	20h,al		;Spezifischer EOI IRQ0
	pop	ds
	pop	esi edx ecx eax
	iret			;nichts weiter tun
@@overlimit:
	sub	eax,[TimerLimit]
	mov	[TimerVal],eax
	mov	al,60h		;TEST
	out	20h,al		;Spezifischer EOI IRQ0
;	sti
;	Debug_Out "OldInt08"
@@ToOldInt:
	pop	ds
	pop	esi edx ecx eax
	jmp	cs:[OldInt08]	;zum OldIntHandler springen (ins Timer-Device)
@@SwapBuf:
;	Debug_Out "SwapBuf"
	xchg	[NextLen],ecx	;[NextLen] auf Null setzen: Puffer frei
	jecxz	@@unhook
	mov	esi,[NextPtr]	;Zeiger laden (wird dann nach BufPtr gesetzt)
	mov	eax,[SimulatedBufSize]
	dec	ecx
	MIN	eax,ecx
	inc	ecx
	mov	[CallbackPrevTick],eax
	jnz	@@play
@@unhook:
	;Timer und Owner (!!) rücksetzen
	mov	eax,[TimerLimit]
	call	ProgT0
	xor	eax,eax
	mov	[OwningVM],eax
	jmp	@@nocallback

@@maycallback:
	mov	ecx,[EobCallback]
	jecxz	@@nocallback	;Nicht verfolgen
	push	ebx
	 mov	ebx,[OwningVM]	;Mitgeben
	 mov	al,60h
	 out	20h,al		;Spezifischer EOI für Master Kanal 0
	 sti
	 push	edi ebp	es
	  ld	es,ds		;ES:=DS
	  call	ecx		;aufrufen
	pop	es ebp edi ebx
	cli
	jmp	@@nocallback
EndProc NewInt08


;Mögliche Codesequenzen für verschiedene Fälle (Mono<->Stereo usw.)
;Mono auf Mono:
;	lodsb
;	[xor	al,80h]
;	out	dx,al
;Stereo auf Stereo
;	lodsw
;	[xor	ax,8080h]
;	out	dx,al
;	shr	edx,16
;	xchg	al,ah
;	out	dx,al
;Mono auf Stereo
;	lodsb
;	[xor	al,80h]
;	out	dx,al
;	shr	edx,16
;	out	dx,al
;Stereo auf Mono (mit Mittensignalbildung)
;	lodsw
;	[xor	8080h]	;jedoch invertierte Bedingung!
;	sar	al,1
;	sar	ah,1
;	add	al,ah	;M:=(L+R)/2 = (L/2)+(R/2)  (hier)
;	xor	al,80h	;wenn LPTDAC-DAC 80h-zentriert (normal)
;	out	dx,al


;**********************************************************
;***  Start- und Stop-Prozeduren			***
;***  "NewPlaying" und "StopPlaying"			***
;**********************************************************

;Timer Kanal 0 auf Zeitkonstante AX programmieren
;VR: F, EAX (oberste 8bits werden nullgesetzt)
ProgT0 proc
	shl	eax,8
	mov	al,34h
	out	43h,al		;Erst-Low-Dann-High-Kommando
	shr	eax,8
	out	40h,al
	xchg	al,ah
	out	40h,al
	xchg	ah,al
	ret
ProgT0 endp

;Abspiel starten oder fortsetzen mit eingetragener Sample-Rate
;Dabei wird der Timer Kanal 0 "hochgestellt" und der OldInt in der gleichen
;(mittleren) Frequenz wie vorher gerufen, um das Windows-Zeitgeber-Gerät VTD
;nicht durcheinanderzubringen.
;PE: EBX: Besitzende Virtuelle Maschine ID
;    ESI: Lineare Adresse des Abspiel-Speichers
;    ECX: Länge des Abspiel-Speichers in Samples (bei Mono demnach
;	in Bytes und bei Stereo in Words)
;PA: CY=1: Fehler:
;	LPTDAC ist von anderer VM belegt
;VR: F, EAX
BeginProc NewPlaying, no_log
	pushfd
	 cli
	 cmp	[OwningVM],ebx	;Besitzer gleich?
	 je	@@c
	 cmp	[OwningVM],0	;Kein Besitzer?
	 jnz	Ferr	;Doch, ein anderer!
	 mov	[OwningVM],ebx	;Besitzer eintragen
	 mov	eax,[TimerAdd]
	 call	ProgT0		;Timer schnell machen
@@c:
	 xor	eax,eax
	 cmp	[BufLen],eax	;Erster Block frei? (CY immer 0)
	 jz	@@d	;ja
	 mov	[NextPtr],esi	;wenn nein dann
	 mov	[NextLen],ecx	; Reserveblock rigoros überschreiben
	 jmp	@@e
@@d:	 mov	[BufPtr],esi
	 mov	[BufLen],ecx
	 push	eax		;immer wenn BufLen gesetzt wird, diese Prozedur:
	  mov	eax,[SimulatedBufSize]
	  dec	ecx
	  MIN	eax,ecx
	  inc	ecx
	  mov	[CallbackPrevTick],eax
	 pop	eax
@@e:	popfd			;Flags (evtl.) freigeben
	clc
	ret
Ferr:	popfd
	stc
	ret
EndProc NewPlaying

;Neue Sample-Rate setzen.
;PE: EAX: Timer-Ticks
;    EBX: VM-Handle (muß passen!)
;PA: CY=1: LPTDAC von anderer VM belegt
;VR: F, EAX
BeginProc SetSampleRate, no_log
	pushfd
	 cli
	 cmp	[OwningVM],ebx	;Gleiches Handle?
	 jnz	@@a
	 cmp	[TimerAdd],eax	;Gleich?
	 je	@@h	;ja, nichts unternehmen
	 call	ProgT0		;Samplerate ggf. "on-line" umstellen
	 jmp	@@w
@@a:
	 cmp	[OwningVM],0	;LPTDAC momentan herrenlos?
	 jnz	Ferr		;nein, FEHLER
@@w:	 mov	[TimerAdd],eax	;ja, abspeichern
@@h:	popfd
	clc			;niemals Fehler
	ret
EndProc SetSampleRate

;PE: AL=Mode-Bits (Mono, Stereo, Surround, weiß der Teufel was noch)
;PA: CY=1: LPTDAC belegt (auch von eigener VM)
BeginProc SetMode
	clc
	ret
EndProc SetMode

;PE: ESI=Callback-Adresse
;    EBX=VM Handle
;PA: CY=1: LPTDAC ist von jemand anderem belegt!
BeginProc SetCallbackProc
	pushfd
	 cli
	 cmp	[OwningVM],0
	 jz	@@a
	 cmp	[OwningVM],ebx
	 jnz	Ferr
@@a:	 mov	[EobCallback],esi
	popfd
	ret
EndProc SetCallbackProc

;"AbortPlaying" bricht (eventuellen) Abspielvorgang ab.
;Dabei wird der Timer Kanal 0 auf den alten Wert zurückprogrammiert.
;PE: EBX: VM-Handle
;PA: CY=1: LPTDAC von anderer VM belegt
;VR: F, EAX
BeginProc AbortPlaying, no_log
	pushfd
	 cli
	 cmp	[OwningVM],0	;Momentan in Besitz?
	 jz	@@e	;nein, nichts tun
	 cmp	[OwningVM],ebx	;EBX paßt?
	 jne	Ferr	;nein: Fehler!
	 mov	eax,[TimerLimit]
	 call	ProgT0		;Timer Kanal 0 auf originale Zeitkonstante
	 xor	eax,eax
	 mov	[OwningVM],eax	;Null, LPTDAC ist nun frei
	 mov	[BufLen],eax
	 mov	[NextLen],eax	;Beide Blocklängen auf Null stellen
@@e:	popfd
	clc
	ret
EndProc AbortPlaying


;Millisekunden-->Timerzählerwert (EAX-->EAX)
ms2ticks proc
	push	ecx edx
	 mov	ecx,TIMERFREQ
	 mul	ecx
	 mov	ecx,1000
	 div	ecx
	pop	edx ecx
	ret
endp

;Timer-Auflösung setzen wird abgefangen,
; um Timer ggf. nach UNSEREN Wünschen nachzuprogrammieren
BeginProc NewSetService
	Trace_Out "SetService"
	call	[OldSetService]
	jmp	CommonTimer
EndProc NewSetService

;Timer-Auflösung rücksetzen wird abgefangen,
; um Timer ggf. nach UNSEREN Wünschen nachzuprogrammieren
BeginProc NewReleaseService
	Trace_Out "ReleaseService"
	call	[OldReleaseService]
CommonTimer:
	jc	@@e	;bei Fehler
	push	eax		;unverändert lassen
	 VxDcall VTD_Get_Interrupt_Period
	 call	ms2ticks	;EAX-->EAX
	 pushfd
	  cli			;Kein Timer-Interrupt hier!
	  mov	[TimerLimit],eax;Aktualisieren (Nachführen)
	  xor	eax,eax
	  mov	[TimerVal],eax	;Neu starten (wie die Hardware es tut?)
	  cmp	[OwningVM],eax	;Null?
	  jz	@@a	;ja: Timer langsam tuckern lassen
	Debug_Halt
	  mov	eax,[TimerAdd]
	  call	ProgT0		;Timer wieder SCHNELL machen!!
@@a:	 popfd
	pop	eax
	clc
@@e:	ret
EndProc NewReleaseService

;Wenn spielende VM stirbt, dann auch die LPTDAC-Ausgabe sterben lassen
;EBX=sterbendes VM-Handle
BeginProc VMDies
	call	AbortPlaying	;killt Ton, wenn VM-Handle paßt
	clc
	ret
EndProc VMDies

VxD_Locked_Code_Ends
;**********************************************************
;***  Spezial-h#s-API-Teil				***
;**********************************************************

VxD_Locked_Data_Seg
;*** Daten für den Gebrauch durch die API ***
CallbackOfs	dd	?
CallbackSeg	dw	?
VxD_Locked_Data_Ends

VxD_Locked_Code_Seg

;berechnet DX (Samplerate) --> (E)AX (Teilerwert)
;PE: DX: Samplerate in Hertz, minimal 1000, maximal 65535
;PA: (E)AX: Teilerwert (Zeitkonstante PIC), stets <65536
;    CY=1: DX fehlerhaft (zu klein)
;VR: F, EDX, EAX
BeginProc CalcTC
	cmp	dx,1000		;Samplerate <1000Hz?? ->Fehler
	jc	@@e
	push	ecx
	 movzx	ecx,dx
	 mov	eax,TIMERFREQ	;Frequenz = Divident für Zeitkonstante
	 xor	edx,edx
	 div	ecx
	pop	ecx
	shl	edx,1		;Rest verdoppeln
	cmp	eax,edx		;Rest>Quotiont/2?
	adc	eax,0		;Wenn ja, dann eins drauf (Runden)
@@e:	ret
EndProc CalcTC

BeginProc SetSampleHertz
	push	eax
	 call	CalcTC		;dx-->eax
	 jc	@@e
	 call	SetSampleRate
@@e:	pop	eax
	ret
EndProc SetSampleHertz

;Aufruf dieser Callback-Routine erfolgt mit
;EBX: VM Handle das LPTDAC besitzenden VM
;     Das LPTDAC ist nicht freigegeben, der Timerinterrupt läuft noch
;	Ein Fortsetzen ist nur mit dem gleichen Handle möglich, dies
;	vermeidet Knacken an den Nahtstellen.
BeginProc ApiCallback, async_service,no_log
	cmp	[CallbackSeg],0
	jz	cbend
	mov	eax,High_Pri_Device_Boost
	mov	ecx,PEF_Wait_Not_Crit
	lea	esi,ApiCall
	xor	edi,edi
	VMMjmp	Call_Priority_VM_Event
EndProc ApiCallback

ApiCall proc
	Push_Client_State
	 VMMcall Begin_Nest_Exec
	 mov	edx,[CallbackOfs]
	 mov	cx,[CallbackSeg]
	 VMMcall Simulate_Far_Call
	 VMMcall Resume_Exec
	 VMMcall End_Nest_Exec
	Pop_Client_State
cbend:	ret
ApiCall endp

; AH=0: Versionsnummer erhalten (CY immer 0)
;	   PA:	Client_AX=100h, Version 1.00 entsprechend
; AH=1: Test, ob LPTDAC belegt (CY immer 0)
;	   PA:	Client_AX=0: LPTDAC frei
;			 =1: LPTDAC von eigener VM belegt
;			 =2: LPTDAC von fremder VM belegt
;			 =3: LPTDAC vom Drucker belegt (Drucker hat Vorrang)
; AH=2: Start Wave-Ausgabe
;	   PE:	Client_DS:ESI: Puffer-Adresse
;		Client_ES:EDI: Callback-Adresse, ES=0: keine
;		Client_ECX: Pufferlänge in SAMPLES (bei mono = Bytes)
;		Client_DX: Samplerate in Hertz
;		Client_AL: Flags
;			Bit7=1: 80h-zentriert
;			Bit1=1: Stereo
;	   PA:	Client_Flags: CY=1 Fehler
;			- Sample-Rate zu niedrig
;			- LPTDAC von fremder VM belegt
;			- kein Stereo vorhanden
; AH=3: Stop Wave-Ausgabe
;	   PA:	Client_Flags: CY=1 wenn LPTDAC von fremder VM belegt ist
; AH=4: Fortsetzen Wave-Ausgabe (Callback-Adresse bleibt)
;	   PE:	Client_DS:ESI: Puffer-Adresse
;		Client_ECX: Pufferlänge in SAMPLES (bei mono = Bytes)
;	   PA:	Client_Flags: CY=1 Fehler
;			- LPTDAC von fremder VM belegt

BeginProc PlayAPI
	mov	ax,[ebp.Client_ax]	;AH=UP-Nummer
	or	ah,ah
	jz	@@ver	;0: Versionsabfrage
	dec	ah
	jz	@@busy	;1: Besetzt-Test
	dec	ah
	jz	@@play	;2: Los- oder Weiterspielen
	dec	ah
	jz	@@stop	;3: Anhalten
	dec	ah
	jz	@@continue;4: Fortsetzen
@@err:	;alles andere: Fehler
	BSET	[ebp.Client_flags],bit 0 ;Carry-Flag setzen
	ret

@@ver:	;AX = Versions-Nummer (AH=High-Teil, AL=Low-Teil)
	mov	[ebp.Client_ax],100h
@@ex:	jc	@@err
@@ok:	BRES	[ebp.Client_flags],bit 0 ;Carry-Flag löschen
	ret

@@busy:	;AX = 0: frei, 1: besetzt, eigene VM, 2: besetzt, fremde VM
	xor	edx,edx
	mov	eax,[OwningVM]
	or	eax,eax
	jz	@@be
	inc	edx
	cmp	eax,ebx
	jz	@@be
	inc	edx
@@be:	mov	[ebp.Client_ax],dx
	jmp	@@ok

@@play:	;Starten bzw. Fortfahren der Soundausgabe
	call	SetMode
	mov	dx,[ebp.Client_dx]	;Samplerate in Hz = Divisor
	call	SetSampleHertz
	jc	@@err
	mov	ax,[ebp.Client_ES]
	mov	[CallbackSeg],ax
	mov	eax,[ebp.Client_EDI]
	mov	[CallbackOfs],eax
@@continue:
	lea	esi,@ApiCallback
	call	SetCallbackProc
	jc	@@err
	Client_Ptr_Flat eax,DS,ESI	;Client-Pointer wandeln
	xchg	esi,eax
	mov	ecx,[ebp.Client_ecx]
	call	NewPlaying
	jmp	@@ex

@@stop:	;Anhalten der Soundausgabe
	call	AbortPlaying
	jmp	@@ex
EndProc PlayAPI

VxD_Locked_Code_Ends


;**********************************************************
;*** SoundBlaster-Emulations-Teil			***
;**********************************************************


VxD_Locked_Data_Seg

;*** Daten für den Gebrauch durch den SoundBlaster-Emulator ***
SBPort		dw	220h	;Portadresse der vermeintlichen SB-Karte
SBIrq		db	7	;IRQ-Jumper... (Standard: 5 bei mir)
SBDma		db	1	;DMA-Kanal
SBVer		dw	202h	;? Stimmt das ?
LastCmd		db	0
LastTC		db	0	;SB-"Samplerate" (TC=256-1000000/Samplerate[Hz])
LastLen		dw	0	;Länge
Dspwb		db	0	;(Noch) Zu schreibende Bytes (0=Kommando)
Dsprb		db	0	;(Noch) Zu lesendes Bytes (0=Lese AA)
	align	4
Dspwp		dd	?	;Schreib-Zeiger
Dsprp		dd	?	;Lese-Zeiger
DmaHand		dd	?	;DMA-Handle
IrqHand		dd	?	;IRQ-Handle
MerkBase	dd	?	;DMA-Basis-Merker
MerkLen		dd	?	;DMA-Länge-Merker
CurBase		dd	?	;Momentane DMA-Basis-Adresse
CurLen		dd	?	;Momentane (Rest-) DMA-Länge
CurMode		dw	?	;Momentaner DMA-Modus (wesentlich: AutoInit-Bit)
SBState		dw	0	;SoundBlaster-Statusbits
;Bit0: DSP Interrupt Prozeß Flag (DSP EOI löscht dieses Flag)
;Bit1: Stereo
;Bit2:
;Bit3: Eingabe (nicht von LPTDAC unterstützt)
;Bit4: Auto-Init-DMA-Modus
;Bit5:
;Bit6: =0: Pause bei DMA-Übertragung (1=SB läuft)
;Bit7: Lautsprecher EIN
;
WarnFlags	dw	0	;Alle Warnungen EIN
;Bit0: Nicht unterstützter DSP-Befehl
captxt$	db "LPTDAC.386 GerΣtetreiber",0
msgcmd$	db "Unbekannter oder nicht unterstⁿtzter SoundBlaster-DSP-Befehl "
hexcmd$	db "??h!",0
dmamod$	db "DMA-Programmierung unpassend, Modus="
hexmod$	db "??h!",0
dmalen$	db "DMA-LΣnge kⁿrzer als DSP-LΣnge, Differenz="
hexdif$	db "????????h!",0
dmalea$	db "DMA-LΣnge kein ganzzahliges Vielfaches der DSP-LΣnge, Rest="
hexres$	db "????????h!",0
msgcon$	db "LPTDAC wird bereits von einer anderen Applikation verwendet; "
	db "der Abspiel-Befehl wird ignoriert.",0


;Verteiler-Tabelle
DspCmdVT:
	UV	0e1h,DC_v	;Version erhalten
	UV	014h,DC_bl	;Ausgabe Mono Unkomprimiert
	UV	091h,DC_bl	;Ausgabe Mono Unkomprimiert (beschleunigt)
	UV	040h,DC_tc	;Zeitkonstante setzen
	UV	048h,DC_bl	;Blocklänge setzen
	UV	01ch,DC_a1	;Auto-Init-DMA-Sound starten
	UV	090h,DC_a1	;Auto-Init-DMA-Sound starten (beschleunigt)
	UV	0dah,DC_a0	;Auto-Init-DMA beenden
	UV	0d1h,DC_s1	;Lautsprecher EIN
	UV	0d3h,DC_s0	;Lautsprecher AUS
	UV	0d0h,DC_p1	;Pause 8bit beginnen
	UV	0d4h,DC_p0	;Pause 8bit beenden
	UV	0f2h,DC_int	;Interrupt generieren (?)
	db	0		;Tabellenende
VxD_Locked_Data_Ends

VxD_Locked_Code_Seg

BeginProc SetSampleTC
	pushad
	 neg	al
	 setz	ah
	 movzx	ecx,ax
	 mov	eax,TIMERFREQ
	 mul	ecx
	 mov	ecx,1000000
	 div	ecx
	 call	SetSampleRate
	popad
	ret
EndProc SetSampleTC

;Register AL,AX oder EAX in Hex nach [EDI] ausgeben (2 Bytes)
;CLD!!, VR:AL,AX oder EAX
WriteHex proc
	ror	eax,16		;"xchg e(ax),ax"
	call	WriteHexWord
	ror	eax,16
WriteHexWord:
	xchg	al,ah
	call	WriteHexByte
	xchg	ah,al
WriteHexByte:
	push	eax
	shr	al,4
	call	@@1
	pop	eax
@@1:	and	al,0fh
	add	al,90h
	daa
	adc	al,40h
	daa
	stosb
	ret
WriteHex endp

;TC2SR wandelt SoundBlaster-Zeitkonstante "TC" in Samplerate um
;PE: AL=SoundBlaster-Zeitkonstante "TC" (muß <>0 sein!)
;PA: EAX=Samplerate in Hz
;VR: F, EAX, EDX
TC2SR proc
	push	ecx
	 neg	al		;AL:=256-AL
	 setz	ah		;wenn AL=0 dann AH:=1 sonst AH:=0
	 movzx	ecx,ax
	 mov	eax,1000000	;1 Million
	 xor	edx,edx
	 div	ecx
	pop	ecx
	shl	edx,1		;Rest verdoppeln
	cmp	eax,edx		;Rest>Quotiont/2?
	adc	eax,0		;Wenn ja, dann eins drauf (Runden)
	ret
TC2SR endp

;PE: ECX: Meldungs-String (nullterminiert)
;    DX: Bit-Nummer in WarnFlags
WarnUser proc
	bts	[WarnFlags],dx	;Keine verschachtelten Aufrufe zulassen
	jc	@@e
	pushad
	 mov	eax,MB_RetryCancel+MB_IconExclamation
	 lea	edi,captxt$
	 lea	esi,WarnEnd	;ein CallBack
	 VxDcall Shell_Message
	popad
@@e:	ret
WarnUser endp

;Callback-Prozedur für Shell_Message, löscht das Reentranzbit bei RETRY
WarnEnd proc
	cmp	eax,IDRetry
	jnz	@@e	;Bit stehen lassen (keine weiteren Warnungen)
	btr	[WarnFlags],dx	;Bit löschen (Warnungen wiederholen lassen)
@@e:	ret
WarnEnd endp

MsgCmd proc
	push	edi
	 lea	edi,hexcmd$
	 cld
	 call	WriteHexByte	;Fehlerhaftes Byte in String patchen
	 lea	ecx,msgcmd$
	 xor	edx,edx		;Warnungs-Nummer 0
	 call	WarnUser
	pop	edi
	ret
MsgCmd endp

ifdef DEBUG
TraceIO proc
	Trace_Out "Portzugriff! EAX=#EAX EBX=#EBX ECX=#ECX EDX=#EDX"
	cmp	[OwningVM],0
;	jz	@@e
	Debug_Halt
@@e:	ret
TraceIO endp
endif

;Unterprogramm-Verteiler, EDI=Unterprogramm-Tabelle, AL=Funktions-Code
;CLD muß gesetzt sein!
upv0:	add	edi,4		;DWORD übergehen
UPV proc
	cmp	byte ptr [edi],1 ;Abschluß-Null?
	jc	@@e
	scasb
	jnz	upv0
	call	[edi]		;anspringen
	clc			;Unterfunktion gefunden
@@e:	ret
UPV endp

BeginProc Dsp_Reset_Trap
	Dispatch_Byte_IO Fall_Through,<@@o>
	mov	al,0ffh		;"Nichts zum Lesen" melden
	clc
	ret
@@o:
	btst	al,bit 0
	jz	@@e	;Nicht rücksetzen
	Trace_Out ">>>DSP RESET"
	mov	[Dspwb],0	;Kommando erwarten (keine Schreibdaten)
	mov	[Dsprb],0	;Keine Lesedaten
	mov	[SBState],0	;STOP
	call	AbortPlaying	;(Eigenen) Sound abbrechen
@@e:	clc
	ret
EndProc Dsp_Reset_Trap

BeginProc Dsp_Command_Trap
	Dispatch_Byte_IO Fall_Through,<@@o>
;	Trace_Out ">>>DSP WriteOK, ",nocrlf
	xor	al,al		;Immer bereiten DSP emulieren
	clc
	ret
@@o:
	cmp	[Dspwb],0	;Bereits Null?
	jz	@@cmd	;Kommando erwartet!
	push	edi
	 mov	edi,[Dspwp]	;Zeiger laden
	 cld
	Trace_Out ">>>DSP WriteByte #AL"
	 stosb
	 mov	[Dspwp],edi
	pop	edi
	dec	[Dspwb]
	jnz	@@nostart	;Wenn noch nicht Null, nichts unternehmen
	cmp	[LastCmd],14h	;Ausgabe angefordert?
	jz	snd
	cmp	[LastCmd],40h	;Zeitkonstante?
	jnz	@@nostart	;nein, auch nichts tun
	mov	al,[LastTC]
	call	SetSampleTC
	jmp	@@e
snd:	;Soundausgabe jetzt starten!!
	Debug_Halt
	call	TestSoundOK
	jc	@@nostart
	lea	esi,@SBCallback
	call	SetCallbackProc
	jc	@@inuse
	call	StartDmaSound
	jnc	@@nostart
@@inuse:
	push	ecx edx
	 lea	ecx,msgcon$
	 mov	dx,4		;Bit 4: Gerätekonkurrenz (wer zuerst kommt...)
	 call	WarnUser
	pop	edx ecx
@@nostart:
	clc
	ret
@@cmd:
	mov	[lastcmd],al	;Mal merken
	Trace_Out ">>>DSP WriteCmd #AL"
	mov	[Dsprb],0	;Kein Byte zum Lesen lieferbar!
	push	edi
	 lea	edi,DspCmdVT	;Kommando-Liste
	 cld
	 call	UPV		;Unterprogramm-Verteiler
	pop	edi
	jnc	@@e	;Nummer gefunden
	Debug_Out "Nicht unterstützte DSP-Funktion #AL"
	call	MsgCmd
@@e:	clc
	ret
EndProc Dsp_Command_Trap

;Unterfunktionen zu den einzelnen Kommandos
DC_v:	;Versionsabfrage
	mov	[Dsprp],OFFSET32 SBVer	;Versionsnummer, hier 2.02, liefern
	mov	[Dsprb],2	;2 Bytes
	ret
DC_bl:	;Blocklänge setzen
	mov	[Dspwp],OFFSET32 LastLen ;hierhin...
	mov	[Dspwb],2	;2 Bytes (Länge) schreiben lassen
	BSET	[SBState],bit 6	;Pause AUS
	ret
DC_tc:	;Zeitkonstante setzen
	mov	[Dspwp],OFFSET32 LastTC ;hierhin...
	mov	[Dspwb],1	;1 Byte (Zeitkonstante) schreiben lassen
	ret
DC_a1:	;Auto-Init-DMA-Sound
	BSET	[SBState],bit 4 + bit 6	;Auto Init Modus Bit setzen, Pause AUS
	jmp	snd
DC_a0:	;Ende des Auto-Init-DMA-Sounds
	btr	[SBState],4	;...löschen
	jnc	EatInt		;und ret
	BSET	[SBState],bit 5	;sonst Nachlauf-Bit setzen
	ret
DC_s1:	;Lautsprecher EIN
	BSET	[SBState],bit 7	;Lautsprecher EIN
	ret
DC_s0:	;Lautsprecher AUS
	BRES	[SBState],bit 7	;Lautsprecher AUS
	ret
DC_p1:	;Pause EIN
	BRES	[SBState],bit 6	;Soundausgabe STOP
	ret
DC_p0:	;Pause AUS
	BSET	[SBState],bit 6	;Soundausgabe FORTFAHREN
	BTST	[SBState],bit 4	;Auto-Init?
	jnz	snd		;ja, weiterspielen
	ret

BeginProc Dsp_Read_Trap
	Dispatch_Byte_IO Fall_Through,<@@o>
	cmp	[Dsprb],0	;Kein Byte da?
	jz	@@aa	;AA liefern
	push	esi
	 mov	esi,[Dsprp]
	 cld
	 lodsb
	 mov	[Dsprp],esi	;Zeiger vorrücken lassen
	pop	esi
	dec	[Dsprb]		;1 Byte weniger
	jmp	@@e

@@aa:	mov	al,0aah		;"bereit" melden, wenn kein String da ist
@@e:
	Trace_Out ">>>DSP ReadByte #AL"
@@o:	clc
	ret			;Ausgaben "durchfallen" lassen
EndProc Dsp_Read_Trap

BeginProc Dsp_ReadOK_Trap
	Dispatch_Byte_IO Fall_Through,<@@o>
;	Trace_Out ">>>DSP ReadOK",nocrlf
	mov	al,80h		;Immerbereites Port melden
	BRES	[SBState],bit 0	;EOI
@@o:	clc
	ret			;Ausgaben "durchfallen" lassen
EndProc Dsp_ReadOK_Trap

;Testet DMA-Sound auf Korrektheit der Parameter
;- DMA-Puffer muß ein ganzzahliges Vielfaches der SB-Blocklänge sein!
;- DMA-Transfer muß eingeschaltet sein, mit STEIGENDEN Adressen
TestSoundOK proc
	pushad
	 mov	al,byte ptr [CurMode]	;DMA-Betriebsart
	 test	al,DMA_masked or DMA_type_write or DMA_AdrDec
	 jz	@@a	;alles okay
	 lea	edi,hexmod$
	 call	WriteHexByte	;Byte einpatchen
	 lea	ecx,dmamod$
	 xor	edx,edx
	 inc	edx		;Nummer 1
	 call	WarnUser
	 Debug_Halt
	 jmp	@@err
@@a:
	 movzx	eax,[LastLen]
	 inc	eax
	 mov	ecx,[CurLen]
	 sub	eax,ecx		;positiv wenn DMA-Länge zu kurz
	 jbe	@@b	;alles okay (ECX≥EAX)
	 lea	edi,hexdif$
	 call	WriteHex	;Differenz patchen
	 lea	ecx,dmalen$
	 mov	dx,2		;Nummer 2
	 call	WarnUser
	 Debug_Halt
	 jmp	@@err
@@b:
	 test	[SBState],bit 4	;Autoinit-Modus?
	 jz	@@c	;aus, keine Rest-Probleme!
	 movzx	ecx,[LastLen]
	 inc	ecx		;Divisor
	 mov	eax,[MerkLen]	;Divident
	 xor	edx,edx
	 div	ecx
	 xchg	eax,edx
	 or	eax,eax		;Rest verblieben?
	 jz	@@c	;nein, okay
	 lea	edi,hexres$
	 call	WriteHex
	 lea	ecx,dmalea$
	 mov	dx,3		;Warnung Nummer 3
	 call	WarnUser
	 Debug_Halt
@@err:	 stc
@@c:	popad
	ret
endp


BeginProc SBCallBack, async_service,no_log
;	Trace_Out ">>>IRQ Trap  ",nocrlf
;bei Auto-Init-SB-Modus weitermachen!
	BTST	[SBState],bit 4	;AutoInit-Modus?
	jz	@@a	;nein
	BTST	[SBState],bit 6	;Momentan Pause?
	jz	@@a	;ja, Ton auslaufen lassen
	call	StartDmaSound	;nein, anhängen
@@a:
DC_int:
	bts	[SBState],0	;Interrupt-In-Service-Flag testen und setzen
	jc	@@Eat	;Bit war gesetzt->keine Verschachtelung!
	mov	eax,[IrqHand]
	VxDjmp	VPICD_Set_Int_Request
@@Eat:	Debug_Out "SB-Interrupt-Verschachtelung (DSP EOI fehlte)"
@@e:
EatInt:	ret
EndProc SBCallBack

BeginProc EoiProc
;	Trace_Out ">>>EOI Trap"
	VxDjmp	VPICD_Clear_Int_Request		;natürlich KEIN "Phys_EOI" !
EndProc EoiProc

ReinitPointers proc
	mov	esi,[MerkBase]
	mov	ecx,[MerkLen]
	jmp	@@1
WritePointers:
	mov	[MerkBase],esi
	mov	[MerkLen],ecx
	mov	[CurMode],dx
@@1:	mov	[CurBase],esi
	mov	[CurLen],ecx
	ret
ReinitPointers endp

;lade ESI mit DMA-CurBase und ECX mit SoundBlaster-Blocklänge
;PA: CY=1 wenn DMA-Puffer zu klein ist
LoadPointers proc
	mov	esi,[CurBase]	;Momentane DMA-Basisadresse
	movzx	ecx,[LastLen]	;Einprogrammierte SB-Blocklänge-1
	inc	ecx		;+1
ifdef DEBUG
	cmp	[CurLen],ecx	;Ausreichend Platz im DMA-Puffer?
	jnc	@@e	;ja
	Debug_Out "Fehlerhafte oder nicht emulierte SB-Programmierung!"
endif
@@e:	ret
LoadPointers endp

;DMA-Zeiger um ECX vergrößern und Länge um ECX verkleinern.
;Beim Erreichen des Endes ggf. AutoInit ausführen
MoveDmaPointers proc
	add	[CurBase],ecx	;DMA-Basis nachrutschen lassen
	sub	[CurLen],ecx	;DMA-Zähler im voraus verkleinern
	jnz	@@e	;Kein EOP (Terminal Count)
	BTST	[CurMode],DMA_AutoInit	;Autoinit-DMA eingeschaltet?
	jz	@@e	;nein, nicht reinitialisieren
	call	ReinitPointers	;Pufferzeiger neu anlegen
@@e:	ret
MoveDmaPointers endp

StartDmaSound proc
	pushad
	 call	LoadPointers
ifdef DEBUG
	 jc	@@e
;	 mov	edx,[TimerAdd]
;	Trace_Out ">>>DSP Start Sound, BASE=#ESI, LEN=#ECX, Ticks=#EDX"
endif
	 call	NewPlaying
	 call	MoveDmaPointers
@@e:	popad
	ret
StartDmaSound endp

;Nichts weiter unternehmen
MaskProc proc
	Trace_Out ">>>Mask_Change, CL=#CL"
;	VxDcall	VPICD_Get_Complete_Status
	ret
MaskProc endp

;Nur Speicherblock festlegen
BeginProc SB_DMA_Trap
	Debug_Out ">>>DMA Trap"
	VxDcall VDMAD_Get_Virt_State	;liefert ESI und ECX
	BTST	dl,DMA_masked		;Maskiert? (bit 0)
	jnz	@@e;unlock	;ja: Puffer freigeben
	call	WritePointers		;nein: Pufferzeiger aktualisieren
;	mov	dl,1			;Eine Ausrichtung
;	VxDcall	VDMAD_Lock_DMA_Region	;Puffer vor Verschiebung sichern
;	jnc	@@e
;	Debug_Out "Nicht geLOCKt, Code #AL"
@@e:	ret
;@@unlock:
;	VxDjmp VDMAD_Unlock_DMA_Region	;DMA-Puffer freigeben
EndProc SB_DMA_Trap


VxD_Locked_Code_Ends

;**********************************************************
;*** Abfangen und Durchreichen regulärer Portzugriffe	***
;**********************************************************
comment |
VxD_Locked_Data_Seg
TimeoutHandle	dd	0
VxD_Locked_Data_Ends

VxD_Locked_Code_Seg
PortHndlr proc
	Dispatch_Byte_IO Fall_Through,<@@o>
	in	al,dx
	jmp	@@c
@@o:	out	dx,al
@@c:	mov	eax,[TimeoutHandle]
	or	eax,eax
	jz	@@n
	VMMcall	CancelTimeOut
@@n:	mov	ecx,2000	;2 Sekunden
	VMMcall	SetVMTimeOut
	mov	[TimeoutHandle],eax
	ret
PortHndlr endp

PortAccTimeOut proc
	xor	eax,eax
	mov	[TimeoutHandle],eax
PortAccTimeOut endp

VxD_Locked_Code_Ends
end comment |

;**********************************************************
;*** Initialisierungs-Teil				***
;**********************************************************
VxD_IData_Seg
LPTDAC$:	dz	"LPTDAC"
sndblst$:	dz	"sndblst.drv"
port$:		dz	"port"		;Vorgabe: 220h
irq$:		dz	"int"		;Vorgabe: 7 (!)
dma$:		dz	"dmachannel"	;Vorgabe: 1
;blast$:dz	"blaster"
;lpt$:	dz	"lpt"
	align	2

IrqDesc	VPICD_IRQ_Descriptor <?,0,\
		OFFSET32 EatInt, OFFSET32 EatInt,\
		OFFSET32 EoiProc, OFFSET32 MaskProc,\
		0,>

Begin_VxD_IO_Table SBIOTable
	VxD_IO	06h,Dsp_Reset_Trap
	VxD_IO	0Ah,Dsp_Read_Trap
	VxD_IO	0Ch,Dsp_Command_Trap
	VxD_IO	0Eh,Dsp_ReadOK_Trap
End_VxD_IO_Table SBIOTable

;Es scheint ja gar, daß die DSP-Register auf 16bit ausgelegt sind!
;Lauter gerade Adressen!

IdtReg		label	fword
IdtLimit	dw	?
IdtBase		dd	?

VxD_IData_Ends

VxD_ICode_Seg
;Patcht die IDT, um den Timerinterrupt einzuklinken
;PE: EBX: VM-Handle (?)
;VR: EAX,ESI
;N:  Interrupts müssen disabled sein!!
BeginProc PatchIDT
	Assert_Ints_Disabled
	;IDT-Eintrags-Adresse bestimmen
	sidt	[IdtReg]	;IDT-Register speichern
	mov	esi,[IdtBase]
	;IDT "hooken", um möglichst kurze Antwortzeiten zu erreichen!
	mov	ax,[esi+8*TIMERINT+6]	;Alten Vektor herausholen
	shl	eax,16
	mov	ax,[esi+8*TIMERINT]
	mov	[OldInt08],eax
	lea	eax,NewInt08
	mov	[esi+8*TIMERINT],ax	;Neuen Vektor einsetzen
	shr	eax,16
	mov	[esi+8*TIMERINT+6],ax
@@e:	ret
EndProc PatchIDT

BeginProc gettimerval
;Ermittelt einen (abwärtszählenden) Zählerstand des 8253 K0.
	pushf
	 cli
	 mov	al,0
	 out	43h,al
	 in	al,40h
	 xchg	ah,al
	 in	al,40h
	 xchg	ah,al
	popf
	ret
EndProc gettimerval

BeginProc getmaxtimer
;Ermittelt maximalen Zählerstand, indem
;auf zwei Zählstände gewartet werden, von dem der zweite _größer_
;ist als der erste. Der zweite ist das Ergebnis.
;Es werden ECX Versuche gemacht und davon das Maximum genommen
	xor	edx,edx		;zur Maximumsuche
	xor	eax,eax		;High-Teil fortan Null
@@l0:	push	edx
	 call	gettimerval
@@l:	 xchg	edx,eax
	 call	gettimerval
	 cmp	eax,edx
	 jbe	@@l
	pop	edx
	cmp	eax,edx
	jc	@@m
	xchg	edx,eax
@@m:	loop	@@l0
	xchg	edx,eax		;Rückgabe
	ret
EndProc getmaxtimer

BeginProc LPTDAC_Init
	;Einhängen in zwei Timer-Services
	mov	eax,VTD_Begin_Min_Int_Period
	lea	esi,NewSetService
	VMMcall	Hook_Device_Service
	mov	[OldSetService],esi
	mov	eax,VTD_End_Min_Int_Period
	lea	esi,NewReleaseService
	VMMcall	Hook_Device_Service
	mov	[OldReleaseService],esi
	VxDcall	VTD_Get_Interrupt_Period	;Ungenau bei Win9x!
	call	ms2ticks
	mov	[TimerLimit],eax
	;Einlesen der Portadressen
	lea	esi,LPTDAC$	;Sektion
	lea	edi,port$	;Schlüssel
	VMMcall	Get_Profile_Hex_Int
	jbe	@@noval
	mov	[LPTDACPorts],eax
@@noval:
	lea	esi,sndblst$	;(fremde) Sektion
	lea	edi,port$
	VMMcall	Get_Profile_Hex_Int
	jbe	@@no_a
	mov	[SBPort],ax
@@no_a:	lea	edi,irq$
	VMMCall	Get_Profile_Decimal_Int
	jbe	@@no_i
	mov	[SBIrq],al
@@no_i:	lea	edi,dma$
	VMMCall	Get_Profile_Decimal_Int
	jbe	@@no_d
	mov	[SBDma],al
@@no_d:
	;Soundblaster-Ports überwachen (hier: nur die DSP-Ports)
	lea	edi,SBIOTable
	mov	esi,edi		;doppeln
	lodsw			;AX=Anzahl
	movzx	ecx,ax		;nach CX
	mov	ax,[SBPort]	;Basisadresse laden
@@l:	add	[esi.VxD_IO_Port],ax	;Portadresse: Basisadresse aufaddieren
	add	esi,size VxD_IO_Struc	;Und wehe, wenn nicht 6 herauskommt!
	loopd	@@l
	VMMcall	Install_Mult_IO_Handlers
	jc	@@e	;Problem entdeckt
	;Druckerports überwachen, für Windows95 Extrawurst braten!
;	lea	esi,PortHndlr
;	movzx	edx,[LPTDACPortLeft]
;	mov	ecx,3
;@@l1:	VMMcall	Install_IO_Handler
;	inc	edx
;	loop	@@l1
	;DMA-Kanal virtualisieren
	movzx	eax,[SBDma]
	lea	esi,SB_DMA_Trap
;	xor	esi,esi		;Volle Virtualisierung!
	VxDcall	VDMAD_Virtualize_Channel
	jc	@@e
	mov	[DmaHand],eax
	;IRQ virtualisieren
	movzx	eax,[SBIrq]
	cmp	al,2
	jnz	@@n2
	mov	al,9		;IRQ2-->IRQ9 am AT (aus VSBD.ASM)
@@n2:	lea	edi,IrqDesc
	mov	[edi.VID_IRQ_Number],ax
	VxDcall	VPICD_Virtualize_IRQ
	mov	[IrqHand],eax
	call	PatchIDT	;Neue ISR für den Timer
	clc
@@e:	ret
EndProc LPTDAC_Init

BeginProc OnInitComplete
	VMMcall	Get_VMM_Version
	cmp	ah,4		;Bugfix 01/02: Extrawurst für Win9x
	jc	@@e
	LD	ecx,8
	call	getmaxtimer	;Das dauert ein bisschen! (max. 1/3 Sekunde)
	inc	eax		;Limit=Endwert+1
	mov	[TimerLimit],eax;Dieser neue Wert ist akkurater
@@e:	ret
EndProc OnInitComplete


;PE: ESI=Stringpointer ASCIIZ
comment |
BeginProc ParseLPTDACPortString
	mov	eax,[esi]	;sei der Stringanfang
	and	eax,0dfdfdfh	;3 Großbuchstaben draus machen
	cmp	eax,'TPL'	;"LPT"
	...
	call	InHex
	jc	...
	mov	[LPTDACPortLeft],ax
	...
	call	InHex
	jc	...
	mov	[LPTDACPortRight],ax
	...usw.usf...
	ret
EndProc ParseLPTDACPortString

InHex proc
	push	edx
	mov	edx,esi
	VMMcall	Convert_Hex_String
	jmp	@@1
InDez:
	push	edx
	mov	edx,esi
	VMMcall	Convert_Decimal_String
@@1:	cmp	esi,edx		;Größer geworden? ->CY=1
	cmc			;Gleich geblieben? ->Fehler!
	xchg	esi,edx
	pop	edx
	ret
InHex endp

;PE: ESI=Stringpointer ASCIIZ
BeginProc ParseBlasterString
@@l:	call	GetNextIdent	;liefert in AL nächsten Kennbuchstabe
	jc	@@e
	BRES	al,bit 5	;Großbuchstabe!
	cmp	al,'A'
	jnz	@@1
	call	InHex
	jc	@@l
	mov	[SBPort],ax
	jmp	@@l
@@1:	cmp	al,'I'
	jnz	@@2
	call	InDez
	jc	@@l
	mov	[SBIrq],al
	jmp	@@l
@@2:	cmp	al,'D'
	jnz	@@3
	call	InDez
	jc	@@l
	mov	[SBDma],al
	jmp	@@l
@@3:	cmp	al,'T'
	jnz	@@l
	call	InDez
	jc	@@l
	mov	[SBVer],ax
	jmp	@@l
@@e:	ret
EndProc ParseBlasterString

|
VxD_ICode_Ends

;**********************************************************
;*** VxD-Service-Funktionen				***
;**********************************************************
VxD_Code_Seg
ifdef DEBUG
BeginProc LPTDAC_Test
	xor	esi,esi		;Wie klingt die RA-Interrupttabelle?
	mov	ecx,2*44100	;2 Sekunden lang (läuft Windows noch weiter?)
				;HiFi-Telefonsound
	jmp	NewPlaying
EndProc LPTDAC_Test
endif

BeginProc LPTDAC_Get_Version, service
	mov	eax,100h
	ret
EndProc LPTDAC_Get_Version

BeginProc LPTDAC_Get_Owner, service
	mov	eax,[OwningVM]
	ret
EndProc LPTDAC_Get_Owner

BeginProc LPTDAC_Get_Ticks, service
	mov	eax,[TimerAdd]
	ret
EndProc LPTDAC_Get_Ticks
VxD_Code_Ends

	end			;Auskommentieren, wenn Initsegment erforderlich

Detected encoding: OEM (CP437)1
Wrong umlauts? - Assume file is ANSI (CP1252) encoded