;**********************************************************
;*** 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
|
|