;/////////////////////////////////////////////////////////////////////////////
;// vmp3d.asm
;//
;// Virtual MPEG Audio Layer3 Device module (link with MP3 Decoder module)
;//
;// 25/10/1999	fOSSiL		Initial version
;// 12/11/1999	fOSSiL		Switched to VPICD_Get_Version start-point
;//				and added better fault-recovery
;// 2000/01/15	The Owl		nasm port
;// 2000/01/16	The Owl		started to add playing related services
;// 2000/01/17	fOSSiL		fixed Trace_Out macros, intersegment relocs
;// 2000/01/17	The Owl		fixed bugs introduced in the nasm port ;-)
;// 2000/01/18	The Owl		added services
;// 2000/01/20	The Owl		rewrote MMSYSTEM and AppyTime event handling
;// 2000/01/21	The Owl		fixed VMP3D_Play_Prev/Next
;// 2000/01/28	The Owl		init fails properly when VDSPD doesn't load
;// 2000/06/21	The Owl		dword aligned data variables


%include "util.mac"
%include "vxdn.inc"
%include "win32n.inc"
%include "vpicdi.inc"
%include "dma.inc"
%include "mp3dec.inc"
%include "vdspd.inc"
%include "fpu.inc"

%define Create_Service_Table_VMP3D
%include "vmp3d.inc"


global VMP3D_Control


bits 32


segment _LDATA

Declare_Virtual_Device VMP3D, 'VMP3D', 1, 0, VMP3D_Device_ID


segment _LTEXT
Begin_Control_Dispatch VMP3D
	Control_Dispatch DEVICE_INIT,VMP3D_Device_Init
	Control_Dispatch SYS_DYNAMIC_DEVICE_INIT,VMP3D_Device_Init
	Control_Dispatch SYS_DYNAMIC_DEVICE_EXIT,VMP3D_Device_Exit
	Control_Dispatch W32_DEVICEIOCONTROL,VMP3D_W32_DeviceIoControl
End_Control_Dispatch


WAVE_FORMAT_PCM	EQU	1

struc WaveFormatEx
wfmt_wFormatTag		resw 1
wfmt_nChannels		resw 1
wfmt_nSamplesPerSec	resd 1
wfmt_nAvgBytesPerSec	resd 1
wfmt_nBlockAlign	resw 1
wfmt_wBitsPerSample	resw 1
endstruc


;
; Global Data
;
segment _LDATA
	align 4
Owner		dd 0
IsPlaying	db 0

	align 4
OrgHwIntProc	dd 0
OrgMaskIRQProc	dd 0

DmaPhysAddr	dd 0
DmaLinAddr	dd 0

IrqNum		dd 0
PicReg		db 0
PicMask		db 0

	align 4
;VID		VPICD_IRQ_Descriptor <5, VPICD_OPT_CAN_SHARE, DspHwIntProc>
IrqTab		dd 0
hIrq		dd 0
hDma		dd 0

FileSize	dd 0
FileLeft	dd 0
BufAddr		dd 0
BufPtr		dd 0

cBlocks		dd 0
CurBlock	dd 0

ListBuf		dd 0
ListPages	dd 0
NextFile	dd 0

MpegBuf		dd 0
MpegPtr		dd 0
MpegLastBytes	dd 0


segment _SDATA
hMMLib		dd 0
_hMMSem		dd 0
waveOutOpen	dd 0
waveOutClose	dd 0
hMMmem		dd 0
LinMMmem	dd 0
IsBusy		dd 0


segment _LDATA
MMwfe:
istruc WaveFormatEx
  at wfmt_wFormatTag,		dw WAVE_FORMAT_PCM
  at wfmt_nChannels,		dw 1
  at wfmt_nSamplesPerSec,	dd 22050
  at wfmt_nAvgBytesPerSec,	dd 22050
  at wfmt_nBlockAlign,		dw 1
  at wfmt_wBitsPerSample,	dw 8
iend
hMMWave		dw 0

segment _LDATA
LastFrame:
istruc FrameInfo
iend


;
; Services
;
segment _LTEXT
VMP3D_Device_Init:
; load DSP vxd
	mov	edx, devname
	mov	eax, VXDLDR_INIT_DEVICE
	VxDCall	VXDLDR_LoadDevice
	jnc	.dsploaded

	cmp	eax, byte VXDLDR_ERR_DUPLICATE_DEVICE
	jz	.dsploaded

%if ADD_DEBUG = 1
	Trace_Out "VDSPD did not load, code #EAX"
%endif

	stc
	retn

.dsploaded:
; Allocate a 64k DMA buffer for DSP
	mov	edx, DmaPhysAddr

; we need mem below 16Meg
; pages=16
; PG_SYS, VM=0
; align=64k
; minpage=0
; maxpage=1000h
; &Dma.PhysAddr
; PAGECONTIG|PAGEUSEALIGN|PAGEFIXED
	VMMCall	_PageAllocate, byte 16, byte PG_SYS, byte 0, byte 0fh, byte 0, dword 1000h, edx, dword PAGECONTIG + PAGEUSEALIGN + PAGEFIXED
	cmp	eax, byte 0
	jnz	@F

%if ADD_DEBUG = 1
	Trace_Out "DMA buffer not alloced"
%endif

	stc
	retn

@@
	mov	[DmaLinAddr], eax

; Allocate a static MPEG buffer (8000h+2000h)
	VMMCall	_PageAllocate, byte 10, byte PG_SYS, byte 0, byte 0, byte 0, byte 0, byte 0, dword PAGEFIXED
	cmp	eax, byte 0
	jnz	@F

%if ADD_DEBUG = 1
	Trace_Out "MPEG buffer not alloced"
%endif

	jmp	.cleanup1

@@
	mov	[MpegBuf], eax

; lookup VPICD irq tables
; we will lookup VPICD_Get_Version address,
; basing on the assumption that Get_Version is close
; to Virtualize_IRQ service
; and scan code from there for a specific MOV opcode
; (hopefully no one will hook Get_Version service)
	mov	eax, VPICD_Device_ID
	VMMCall	Get_DDB

	cmp	ecx, byte 0
	jnz	@F

%if ADD_DEBUG = 1
	Trace_Out "Get_DBB failed"
%endif
	jmp	.cleanup2

@@
	mov	edi, [ecx + DDB_Service_Table_Ptr]
	mov	edi, dword [edi + (@@VPICD_Get_Version & 0xFFFF)*4]
	
; look for mov esi,<mem>[ecx*4] opcode
	mov	ecx, 1000h
	mov	al, 8bh

.again:
	repne	scasb

%if ADD_DEBUG = 1
	Trace_OutNE "MOV pattern not found"
%endif

	jne	near .cleanup2

	cmp	word [edi], 8d34h
	jne	.again

	mov	edi, [edi+2]	; get irq table addr
	cmp	edi, 0C0000000h	; lame check, but at least something
	jb	near .cleanup3

; hopefully, this is the table of IRQStruct ptrs
	mov	[IrqTab], edi

	mov	edi, [edi]	; check table layout through IRQ0's structs
	cmp	edi, 0C0000000h	; lame check, but at least something
	jb	near .cleanup3

	mov	eax, [edi + VID_IRQCB.hIrqOwner]
	cmp	eax, 0C0000000h	; lame check, but at least something
	jb	.cleanup3

	cmp	dword [eax + VID_IRQ_Struct.DbgStr], 51524953h	; 'SIRQ'
	jne	.cleanup3

; init MPEG decoder
	push	dword ReadStreamData
	call	_MpegSetReadProc@4

; init AppyTime synchronization semaphore and load MMSYSTEM
	cmp	dword [hMMLib], byte 0
	jne	@F

	VxDCall	_SHELL_CallAtAppyTime, dword AppyLoadMMLib, byte 0, byte 0, byte 0

	cmp	eax, byte 0
	jne	@F

%if ADD_DEBUG = 1
	Trace_Out "Init appy event-schedule failed"
%endif

	jmp	short .cleanup2

@@
;	cmp	dword [hMMSem], byte 0
;	jne	@F
;
;	xor	ecx, ecx
;	VMMCall	Create_Semaphore
;
;%if ADD_DEBUG = 1
;	Trace_OutC "MM Semaphore not created"
;%endif
;
;	jc	.cleanup2
;
;	mov	[hMMSem], eax
;
;@@

%if ADD_DEBUG = 1
	Trace_Out "MP3 vxd init success"
%endif

	clc
	retn

.cleanup3:
%if ADD_DEBUG = 1
	Trace_Out "MOV pattern found, but VPICD structs are invalid or of unknown format"
%endif

.cleanup2:
; free MPEG buffer
	VMMCall	_PageFree, dword [MpegBuf], byte 0

.cleanup1:
; free DMA buffer
	VMMCall	_PageFree, dword [DmaLinAddr], byte 0

	stc
	retn


segment _LDATA
devname	db 'vdspd.vxd',0


segment _LTEXT
AppyLoadMMLib:
; PROC C Public, dwRefData:DWORD, dwFlags:DWORD
	cmp	dword [hMMLib], byte 0
	je	@F

%if ADD_DEBUG = 1
	Trace_Out "MMSYSTEM already loaded"
%endif

	retn

@@
	VxDCall	_SHELL_LoadLibrary, dword libname

	cmp	eax, byte 32
	jae	@F

%if ADD_DEBUG = 1
	Trace_Out "MMSYSTEM did not load, code #EAX"
%endif

	retn

@@
	mov	[hMMLib], eax

	VxDCall _SHELL_GetProcAddress, dword [hMMLib], dword fnopen
	mov	[waveOutOpen], eax

	VxDCall _SHELL_GetProcAddress, dword [hMMLib], dword fnclose
	mov	[waveOutClose], eax

	VxDCall	_SHELL_LocalAllocEx, byte LMEM_FIXED, byte WaveFormatEx_size + 2, dword MMwfe

	cmp	eax, byte 0
	jne	@F

%if ADD_DEBUG = 1
	Trace_Out "Cannot alloc 16bit mem"
%endif

	retn

@@
	mov	[hMMmem], eax
	mov	[LinMMmem], edx

%if ADD_DEBUG = 1
	Trace_Out "MMSYSTEM loaded at AppyTime"
%endif

	retn


segment _LDATA
libname	db 'MMSYSTEM',0
fnopen	db 'WAVEOUTOPEN',0
fnclose	db 'WAVEOUTCLOSE',0


segment _LTEXT
VMP3D_Device_Exit:
; cleanup DMA buffer
	VMMCall	_PageFree, dword [DmaLinAddr], byte 0
	VMMCall	_PageFree, dword [ListBuf], byte 0
	VMMCall	_PageFree, dword [MpegBuf], byte 0
	xor	eax, eax
	clc
	retn


;segment _PTEXT	; nasm intersegment relocs in same object are bad,
		; better keep this in _LTEXT for now
;
; Win32 DeviceIoControl Dispatcher
;
VMP3D_W32_DeviceIoControl:
	mov	ecx, [esi + DIOCParams.dwIoControlCode]
	mov	ebx, [esi + DIOCParams.VMHandle]
	mov	ebp, [esi + DIOCParams.Internal1]

	cmp	ecx, byte DIOC_OPEN
	jnz	@F

	cmp	dword [Owner], byte 0

%if ADD_DEBUG = 1
	Trace_OutNE "Device is already owned"
%endif
	jne	.err			; another VM has it
	    
	mov	[Owner], ebx

	VxDCall VDSPD_Get_IRQ
	call	HookIrq

%if ADD_DEBUG = 1
	  Trace_OutC "HookIrq failed"
%endif
	jc	.err

	xor	eax, eax
	retn

@@
	cmp	ecx, byte DIOC_CLOSEHANDLE
	jnz	@F

	mov	eax, 1			; release wave dev
	call	StopMpeg
	call	UnhookIrq
	
	mov	dword [Owner], 0

	xor	eax, eax
	retn

@@
; get Function #
	and	ecx, 0x3FFC

	cmp	ecx,byte 4*10
	ja	.err

	jmp	[W32_API+ecx]

.err:
	cmp	eax, byte 1	; eax cannot be == 0 on error
	sbb	eax, byte 0
	retn


segment _LDATA
	align 4
W32_API:
	dd VMP3D_W32_DeviceIoControl.err
	dd VMP3D_W32_DeviceIoControl.err	; W32_PlayWave
	dd W32_SetList
	dd W32_Play
	dd W32_Stop
	dd W32_Pause
	dd W32_Resume
	dd W32_GetMpegInfo
	dd W32_GetStreamPtr
	dd W32_SetStreamPtr
	dd W32_GetStatusInfo


segment _LTEXT
W32_SetRetCode:
;Assume	esi:PTR DIOCParams
	cmp	dword [esi + DIOCParams.cbOutBuffer], byte 4
	jae	@F

	retn

@@
	mov	ecx, [esi + DIOCParams.lpvOutBuffer]	; write to out-buf
	cmp	ecx, byte 0
	jz	@F

	mov	[ecx], eax
	
@@
	mov	ecx, [esi + DIOCParams.lpcbBytesReturned] ; write # of bytes returned
	cmp	ecx, byte 0
	jz	@F

	mov	dword [ecx], 4

@@
	retn


W32_SetList:
;Assume	esi:PTR DIOCParams
	or	eax, byte -1
	mov	edx, [esi + DIOCParams.cbInBuffer]
	cmp	edx, byte 4
	jae	@F

	retn

@@
	mov	edi, [esi + DIOCParams.lpvInBuffer]
	cmp	edi, byte 0
	jnz	@F

	retn

@@
	bts	dword [IsBusy], 0
	jnc	@F

	retn

@@
	add	edx, 0fffh
	shr	edx, 12
	cmp	edx, [ListPages]
	jbe	.norealloc

	mov	[ListPages], edx
	VMMCall	_PageFree, dword [ListBuf], byte 0
	VMMCall	_PageAllocate, dword [ListPages], byte PG_SYS, byte 0, byte 0, byte 0, byte 0, byte 0, dword PAGEFIXED
	cmp	eax, byte 0
	jnz	@F

%if ADD_DEBUG = 1
	Trace_OutZ "List buffer not alloced"
%endif

	jmp	short .err1

@@
	mov	[ListBuf], eax

.norealloc:
; copy list
	mov	ecx, [esi + DIOCParams.cbInBuffer]
	push	esi
	mov	esi, [ListBuf]
	xchg	esi, edi
	rep	movsb
	pop	esi
	
; update ptrs
	mov	edi, [ListBuf]
	mov	edx, edi
	mov	ecx, [edi]
	add	edi, byte 4

@@
	add	[edi], edx
	add	edi, byte 4
	loop	@B
	
	mov	dword [NextFile], 0

	xor	eax, eax

.svcret1:
	call	W32_SetRetCode
	btr	dword [IsBusy], 0
	retn

.err1:
	cmp	eax, byte 1
	sbb	eax, byte 0
	jmp	short .svcret1


W32_Play:
;Assume	esi:PTR DIOCParams
	or	eax, byte -1
	cmp	dword [esi + DIOCParams.cbInBuffer], byte 4
	jae	@F

	retn

@@
	mov	edi, [esi + DIOCParams.lpvInBuffer]
	cmp	edi, byte 0
	jnz	@F

	retn

@@
	bts	dword [IsBusy], 0
	jnc	@F

	retn

@@
	mov	eax, [edi]
	call	SetIndex
	jc	@F

	call	PlayMpegIndex
	jc	@F

	xor	eax, eax

@@
	call	W32_SetRetCode
	btr	dword [IsBusy], 0
	retn


W32_Stop:
;Assume	esi:PTR DIOCParams
	or	eax, byte -1
	cmp	byte [IsPlaying], 0
	jz	@F

	bts	dword [IsBusy], 0
	jc	@F

	mov	eax, 1
	call	StopMpeg
	xor	eax, eax

	btr	dword [IsBusy], 0

@@
	call	W32_SetRetCode
	retn


W32_Pause:
;Assume	esi:PTR DIOCParams
	or	eax, byte -1
	cmp	byte [IsPlaying], 0
	jz	@F

	VxDCall	VDSPD_Pause
	mov	eax, -1
	jc	@F

	xor	eax, eax

@@
	call	W32_SetRetCode
	retn


W32_Resume:
;Assume	esi:PTR DIOCParams
	or	eax, byte -1
	cmp	byte [IsPlaying], 0
	jz	@F

	VxDCall	VDSPD_Resume
	mov	eax, -1
	jc	@F

	xor	eax, eax

@@
	call	W32_SetRetCode
	retn


W32_GetStreamPtr:
;Assume	esi:PTR DIOCParams
	or	eax, byte -1
	cmp	byte [IsPlaying], 0
	jz	@F

	mov	eax, [BufPtr]

@@
	call	W32_SetRetCode

	cmp	eax, byte 0
	jl	@F		; error

	xor	eax, eax

@@
	retn


W32_SetStreamPtr:
;Assume	esi:PTR DIOCParams
	mov	edi, [esi + DIOCParams.lpvInBuffer]
	or	eax, byte -1
	cmp	dword [esi + DIOCParams.cbInBuffer], byte 4
	jb	@F

	cmp	edi, byte 0
	jz	@F

	cmp	byte [IsPlaying], 0
	jz	@F

	bts	dword [IsBusy], 0
	jc	@F

	mov	eax, [edi]
	call	SetPtr

	btr	dword [IsBusy], 0

@@
	call	W32_SetRetCode
	retn


W32_GetMpegInfo:
;Assume	esi:PTR DIOCParams
	mov	edi, [esi + DIOCParams.lpvOutBuffer]
;Assume	edi:PTR MpegInfo

	or	eax, byte -1
	cmp	dword [esi + DIOCParams.cbOutBuffer], byte MpegInfo_size
	jb	@F

	cmp	edi, byte 0
	jz	@F

	cmp	byte [IsPlaying], 0
	jz	@F

	push	esi
	push	edi
	add	edi, byte 4			; sizeof MpegInfo.RetCode
	mov	ecx, (MpegInfo_size - 4) / 4
	mov	esi, LastFrame
	rep	movsd
	pop	edi
	pop	esi
	mov	eax, [FileSize]
	mov	[edi + MpegInfo.StreamSize], eax

	xor	eax, eax
	
@@
	call	W32_SetRetCode
	mov	ecx, [esi + DIOCParams.lpcbBytesReturned] ; write # of bytes returned
	cmp	eax, byte 0
	jnz	@F

	cmp	ecx, byte 0
	jz	@F

	mov	dword [ecx], MpegInfo_size

@@
	retn


W32_GetStatusInfo:
	or	eax, byte -1
	cmp	dword [esi + DIOCParams.cbOutBuffer], byte StatusInfo_size
	jb	.setretcode

	mov	edi, [esi + DIOCParams.lpvOutBuffer]
	test	edi, edi
	jz	.setretcode

	push	dword [NextFile]
	pop	dword [edi+StatusInfo.Track]

	push	dword [BufPtr]
	pop	dword [edi+StatusInfo.Progress],

	mov	al,[IsPlaying]
	mov	[edi+StatusInfo.IsPlaying], al

	mov	al,[IsBusy]
	mov	[edi+StatusInfo.IsBusy], al

	xor	eax, eax

.setretcode:
	call	W32_SetRetCode
	mov	ecx, [esi + DIOCParams.lpcbBytesReturned] ; write # of bytes returned

	cmp	eax, byte 0
	jnz	@F

	cmp	ecx, byte 0
	jz	@F

	mov	dword [ecx], StatusInfo_size

@@
	retn


segment _LTEXT
SetPtr:
; EAX = index
	cmp	eax, [FileSize]
	jb	@F

	or	eax, byte -1
	stc
	retn

@@
; no interruptions now, please =)
	pushfd
	cli
	mov	[BufPtr], eax
	mov	ecx, [FileSize]
	sub	ecx, eax
	mov	[FileLeft], ecx
	popfd

	xor	eax, eax
	clc
	retn


StopMpeg:
; EAX = 1 : release wave device
	cmp	byte [IsPlaying], 1
	jnz	@F

	VxDCall	VDSPD_Stop
	mov	byte [IsPlaying], 0

@@
	cmp	eax, byte 1
	jnz	@F

	cmp	word [hMMWave], 0
	jz	@F

	VxDCall _SHELL_CallAtAppyTime, dword AppyWaveClose, byte 0, byte 0, byte 0
	cmp	eax, byte 0

%if ADD_DEBUG = 1
	Trace_OutE "Let-go Wave appy event-schedule failed"
%endif

	jz	@F

;	mov	eax, [hMMSem]
;	mov	ecx, BLOCK_SVC_INTS | BLOCK_ENABLE_INTS
;	VMMCall	Wait_Semaphore
	VMMCall	_BlockOnID, dword hMMLib, dword BLOCK_SVC_INTS | BLOCK_ENABLE_INTS

@@
	mov	dword [MpegPtr], 0
	cmp	dword [BufAddr], byte 0
	jz	@F

	VMMCall	_PageFree, dword [BufAddr], byte 0
	mov	dword [BufAddr], 0

@@
	retn


SetIndex:
; EAX = index
	push	edx

	mov	edx, [ListBuf]
	cmp	edx, byte 0
	jz	@F

	cmp	eax, [edx]
	jae	@F

	mov	[NextFile], eax

	pop	edx
	xor	eax, eax
	clc
	retn

@@
	pop	edx
	or	eax, byte -1
	stc
	retn


SetIndexNext:
	push	edx

	mov	edx, [ListBuf]
	cmp	edx, byte 0
	jnz	@F

	pop	edx
	or	eax, byte -1
	stc
	retn

@@
	inc	dword [NextFile]
	mov	eax, [NextFile]

	cmp	eax, [edx]
	jb	@F

	and	dword [NextFile], byte 0

@@
	pop	edx
	xor	eax, eax
	clc
	retn


SetIndexPrev:
	push	edx

	mov	edx, [ListBuf]
	cmp	edx, byte 0
	jnz	@F

	pop	edx
	or	eax, byte -1
	stc
	retn

@@
	sub	dword [NextFile], byte 1
	jnc	@F

	mov	eax, [edx]
	dec	eax
	mov	[NextFile], eax

@@
	pop	edx
	xor	eax, eax
	clc
	retn


PlayMpegIndex:
	pushad

	mov	esi, [ListBuf]
	mov	eax, [NextFile]
	lea	eax, [esi+eax*4+4]

	mov	esi, [eax]	; pointer to file name
	jmp	short PlayMpeg.NoRegs

PlayMpeg:
; ESI = filename
	pushad

.NoRegs:
; acquire wave device if not done yet
	cmp	word [hMMWave], 0
	jnz	.hasdevice

	VxDCall _SHELL_CallAtAppyTime, dword AppyWaveOpen, byte 0, byte 0, byte 0
	cmp	eax, byte 0
	jnz	@F

%if ADD_DEBUG = 1
	Trace_Out "Grab Wave appy event-schedule failed"
%endif

	mov	eax, -5
	jmp	.err_noclose

@@
; wait for appy to finish
;	mov	eax, [hMMSem]
;	mov	ecx, BLOCK_SVC_INTS | BLOCK_ENABLE_INTS
;	VMMCall	Wait_Semaphore
	VMMCall	_BlockOnID, dword hMMLib, dword BLOCK_SVC_INTS | BLOCK_ENABLE_INTS

	cmp	word [hMMWave], 0
	jnz	.hasdevice

	mov	eax, -5
	jmp	.err_noclose

.hasdevice:
	xor	eax, eax	; keep the device
	call	StopMpeg

; open file
	mov	eax, R0_OPENCREATFILE
	mov	ebx, 2040h		; read only|share:deny none|no INT24
	mov	ecx, 0020h		; archive
	mov	edx, 0001h		; open|fail
	VxDCall IFSMgr_Ring0_FileIO

%if ADD_DEBUG = 1
	Trace_OutC "Open file failed, code #EAX"
%endif	

	jc	near .err_noclose

; save handle for future
	mov	ebx, eax

	mov	eax, R0_GETFILESIZE
	VxDCall	IFSMgr_Ring0_FileIO
	jc	near .err_close

	mov	[FileSize], eax
	
	add	eax, 0fffh
	shr	eax, 12
	VMMCall	_PageAllocate, eax, byte PG_SYS, byte 0, byte 0, byte 0, byte 0, byte 0, dword PAGEFIXED
	cmp	eax, byte 0
	jnz	@F

%if ADD_DEBUG = 1
	Trace_Out "File buffer alloc failed"
%endif	
	mov	eax, 8			; Not enough mem
	jmp	.err_close

@@
	mov	[BufAddr], eax

; read entire file in
	mov	esi, eax		; buffer
	mov	eax, R0_READFILE
	mov	ecx, [FileSize]		; bytes to read
	xor	edx, edx		; file ofs
	VxDCall	IFSMgr_Ring0_FileIO

%if ADD_DEBUG = 1
	Trace_OutC "Read file failed, code #EAX"
%endif	

	jc	near .err_close

	mov	eax, R0_CLOSEFILE
	VxDCall	IFSMgr_Ring0_FileIO
	
; reset vars
	mov	ecx, [FileSize]
	mov	[FileLeft], ecx
	xor	ecx, ecx
	mov	[CurBlock], ecx
	mov	[cBlocks], ecx
	mov	[BufPtr], ecx
	
	call	InitMpeg
	jc	near .err_noclose

	call	MpegNextBlock
	call	MpegNextBlock

; init DMA for auto-init xfer
	VxDCall	VDSPD_Get_DMA
	mov	ebx, [DmaPhysAddr]
	mov	ecx, 10000h
	call	DmaInit

	mov	byte [IsPlaying], 1

	mov	eax, [hIrq]
	VxDCall	VPICD_Physically_Unmask

	mov	eax, [LastFrame + FrameInfo.SampFreq]
	mov	ebx, [LastFrame + FrameInfo.Chans]
	shr	ebx, 1		; set mono/stereo bit
	mov	edx, [LastFrame + FrameInfo.BitsPerSample]
	shr	edx, 4
	shl	edx, 1
	or	ebx, edx	; set 16bit-play bit
	mov	ecx, 8000h
	VxDCall	VDSPD_Play

	xor	eax, eax
	mov	[esp + CRS.EAX], eax

	popad
	clc
	retn		

.err_close:
	push	eax
	mov	eax, R0_CLOSEFILE
	VxDCall	IFSMgr_Ring0_FileIO
	pop	eax

.err_noclose:
	mov	[esp + CRS.EAX], eax

	popad
	stc
	retn


WAVE_MAPPER	EQU	-1

struc WaveOpenArgs
woa_fdwOpen	resd 1
woa_dwInstance	resd 1
woa_dwCB	resd 1
woa_lpWaveFmt	resd 1
woa_uDeviceID	resw 1
woa_lphWaveOut	resd 1
endstruc

struc WaveCloseArgs
wca_hWaveOut	resw 1
endstruc


AppyWaveOpen:
; PROC C Public, dwRefData:DWORD, dwFlags:DWORD
; setup call stack
	mov	eax, [hMMmem]
	mov	[woa + woa_lpWaveFmt], eax
	add	eax, byte WaveFormatEx_size
	mov	[woa + woa_lphWaveOut], eax
	
; init hWaveOut in case call fails
	mov	edx, [LinMMmem]
	mov	word [edx + WaveFormatEx_size], 0
        push	edx
	VxDCall	_SHELL_CallDll, byte 0, dword [waveOutOpen], byte WaveOpenArgs_size, dword woa
	pop	edx
	mov	cx, [edx + WaveFormatEx_size]

%if ADD_DEBUG = 1
	cmp	eax, byte 0
	jnz	@F

	cmp	cx, 0
	jnz	@F

	Trace_Out "waveOutOpen failed"
@@
%endif

	mov	[hMMWave], cx

%if ADD_DEBUG = 1
	Trace_Out "waveOutOpen succeeded at AppyTime"
%endif

;	mov	eax, [hMMSem]
;	VMMCall	Signal_Semaphore
	VMMCall	_SignalID, dword hMMLib
	retn


segment _LDATA
	align 4
woa:
istruc WaveOpenArgs
  at woa_fdwOpen,	dd 0
  at woa_dwInstance,	dd 0
  at woa_dwCB,		dd 0
  at woa_lpWaveFmt,	dd 0
  at woa_uDeviceID,	dw WAVE_MAPPER
  at woa_lphWaveOut,	dd 0
iend


segment _LTEXT
AppyWaveClose:
; PROC C Public, dwRefData:DWORD, dwFlags:DWORD
	mov	ax, [hMMWave]
	mov	[wca + wca_hWaveOut], ax
	VxDCall	_SHELL_CallDll, byte 0, dword [waveOutClose], byte WaveCloseArgs_size, dword wca
	mov	word [hMMWave], 0

%if ADD_DEBUG = 1
	Trace_Out "waveOutClose succeeded at AppyTime"
%endif

;	mov	eax, [hMMSem]
;	VMMCall	Signal_Semaphore
	VMMCall	_SignalID, dword hMMLib
	retn


segment _LDATA
	align 4
wca:
istruc WaveCloseArgs
iend


segment _LTEXT
InitMpeg:
	VxDCall	VDSPD_Is_Stereo
	mov	edi, eax
	xor	edi, byte 1		; not

	VxDCall	VDSPD_Is_16bit
	mov	esi, eax
	xor	esi, byte 1		; not

	xor	ebx, ebx

.loop1:
	push	byte 0
	push	edi
	push	esi
	push	ebx
	push	dword 1234h		; dummy file handle
	call	_MpegBegin@20
	and	eax, 0C0000000h
	cmp	eax, 0C0000000h
	je	.err1

	call	NextFrame
	jc	.err1

	VxDCall	VDSPD_Get_Rate

	cmp	eax, [LastFrame + FrameInfo.SampFreq]
	jae	.ret1			; card freq supported, ok

	push	dword 1234h
	call	_MpegEnd@4
	inc	ebx
	mov	dword [MpegPtr], 0
	mov	dword [BufPtr], 0
	jmp	short .loop1

.ret1:
	clc
	retn

.err1:
	stc
	retn


NextFrame:
	push	edi
	mov	edi, [MpegBuf]
	add	edi, [MpegPtr]

@@
	push	dword LastFrame
	push	dword MpegLastBytes
	push	edi
	call	_MpegNextFrame@12
	mov	edx, eax
	and	edx, 0C0000000h
	cmp	edx, 0C0000000h 	; check for error
	jz	@F

	cmp	eax, MPEG_ENDOFTRACK
	je	@F

	cmp	dword [MpegLastBytes], byte 0
	jz	@B

	mov	eax, [MpegLastBytes]
	add	[MpegPtr], eax
	clc
	pop	edi
	retn

@@
	stc
	pop	edi
	retn


MpegNextBlock:
@@
	call	NextFrame
	jc	near .err1

	cmp	dword [MpegPtr], 0x8000
	jb	@B

	mov	edx, [MpegPtr]
	mov	ecx, 8000h
	sub	edx, ecx
	mov	[MpegPtr], edx

; copy wave-form to DMA Buffer
	mov	edi, [DmaLinAddr]
	mov	esi, [MpegBuf]
	add	edi, [CurBlock]
	xor	[CurBlock], ecx	; update current block

	cld
	shr	ecx, 2
	rep	movsd

	inc	dword [cBlocks]
	
; move wave-form leftovers (above 8000h) to the begining
	mov	edi, [MpegBuf]
	mov	ecx, edx
	and	edx, 3
	shr	ecx, 2
	rep	movsd
	cmp	edx, byte 0
	jz	@F

	mov	ecx, edx
	rep movsb

@@
	retn

.err1:
; end the stream
	mov	dword [FileLeft], 0
	mov	ecx, [FileSize]
	inc	ecx
	mov	[BufPtr], ecx
	retn


	align 4
DspHwIntProc:
	pushad

	call	OnDspIrq
	jnc	@F

	popad
	jmp	dword [OrgHwIntProc]

@@
	mov	eax, [esp+CRS.EAX]
	VxDCall	VPICD_Phys_EOI

	popad
	clc
	retn


;
; this mess simulates a HOOK_PROC
;
	jmp	short HookedMaskIRQ	; *MUST* assemble to EB,06 or EB,0A
	jmp	[OrgMaskIRQProc]

%if ADD_DEBUG = 1
	dd 0
%endif

HookedMaskIRQ: 
; LOCKED, HOOK_PROC, OrgMaskIRQProc
; prevents our irq from being masked
;Assume	eax:PTR VID_IRQ_Struct

	cmp	byte [IsPlaying], 1
	jne	IgnoreIRQ

	push	ecx
	mov	ecx, [eax + VID_IRQ_Struct.CtrlBlock]
	mov	ecx, [ecx + VID_IRQCB.nIRQ]
	cmp	ecx, [IrqNum]
	pop	ecx

	jne	IgnoreIRQ

	retn	; do nothing

IgnoreIRQ:
	jmp	[OrgMaskIRQProc]


HookIrq:
; EAX = irq #
; EBX = hVM
; EBP = CRS

	mov	[IrqNum], eax

; get PIC addr
	mov	ecx, eax
	shl	cl, 5
	mov	ch, 40h
	rcr	ch, 1
	shr	cl, 5
	mov	[PicReg], ch

; get PIC mask
	mov	ah, 1
	shl	ah, cl
	mov	[PicMask], ah

; hook VPICD_Physically_Mask to prevent masking of our irq
	GetDeviceServiceOrdinal eax, VPICD_Physically_Mask
	mov	esi, HookedMaskIRQ
	VMMCall	Hook_Device_Service

%if ADD_DEBUG = 1
	Trace_OutC "Hooking VPICD_Physically_Mask failed"
%endif

	jnc	@F

	retn

@@
; lookup virtualized irq handle
	mov	edi, [IrqTab]
        mov	eax, [IrqNum]
	mov	edi, [edi+eax*4]	; get VID_IRQCB ptr
	mov	eax, [edi + VID_IRQCB.hIrqOwner]

	cmp	dword [eax + VID_IRQ_Struct.DbgStr], 51524953h	; 'SIRQ'
	jne	.cleanup1

; we got our int, now hook Hw_Int_Proc
	mov	[hIrq], eax
	pushfd
	cli
	mov	ecx, [eax + VID_IRQ_Struct.Hw_Int_Proc]
	mov	[OrgHwIntProc], ecx
	mov	ecx, DspHwIntProc
	mov	[eax + VID_IRQ_Struct.Hw_Int_Proc], ecx
	popfd

        clc
	retn

.cleanup1:

%if ADD_DEBUG = 1
	Trace_Out "VPICD hack failed, invalid or unknown struct"
%endif

; unhook VPICD_Physically_Mask
	GetDeviceServiceOrdinal eax, VPICD_Physically_Mask
	mov	esi, HookedMaskIRQ
	VMMCall	Unhook_Device_Service
	
	stc
	retn


UnhookIrq:
; unhook Hw_Int_Proc
	pushfd
	cli
	mov	edx, [hIrq]
	mov	eax, [OrgHwIntProc]
	mov	[edx + VID_IRQ_Struct.Hw_Int_Proc], eax
	popfd

; unhook VPICD_Physically_Mask
	GetDeviceServiceOrdinal eax, VPICD_Physically_Mask
	mov	esi, HookedMaskIRQ
	VMMCall	Unhook_Device_Service

%if ADD_DEBUG = 1
	Trace_OutC "Unhooking VPICD_Physically_Mask failed"
%endif

	retn


	align 4
OnDspIrq:
	cmp	byte [IsPlaying], 1
	je	@F

	stc
	retn

@@
	VxDCall	VDSPD_Check_Int
	jnc	@F

	stc
	retn

@@
; save fpu state (save TS)
	mov	eax, cr0
	push	eax
	clts
	sub	esp, byte FPU_Status_PM32_size + FPU_STx_size
	fnsave	[esp]

	fninit
	call	MpegNextBlock

; now restore FPU state, first clear possible pending exceptions
	xor	al, al
	xchg	al, [esp + FPU_Status_PM32.StatusLo]
	frstor	[esp]

; now restore status with possible pending exceptions
	xchg	al, [esp + FPU_Status_PM32.StatusLo]
	fldenv	[esp]
	add	esp, byte FPU_Status_PM32_size + FPU_STx_size
	pop	eax
	mov	cr0, eax	; restore CR0.TS
	
	dec	dword [cBlocks]
	jnz	.nostop

	VxDCall	VDSPD_Stop

.nostop:
	clc
	retn


	align 4
ReadStreamData:
; returns number of bytes read or MPEG_ENDOFSTREAM
; PROC Stdcall, hFile:DWORD, Buffer:PTR BYTE, BufferSize:DWORD

%define hFile esp+4+12
%define Buffer esp+8+12
%define BufferSize esp+12+12

	push	esi
	push	edi
	push	ebx

	mov	ecx, [FileLeft]
	cmp	ecx, byte 0
	jnz	@F

	mov	eax, MPEG_ENDOFTRACK
	pop	ebx
	pop	edi
	pop	esi
	retn    12

@@
	mov	eax, [BufferSize]
	cmp	ecx, eax
	jb	.block_ok

	mov	ecx, eax

.block_ok:
	sub	[FileLeft], ecx

	mov	esi, [BufAddr]	; calc next block ptr
	add	esi, [BufPtr]
	add	[BufPtr], ecx	; update buffer ptr
	mov	edi, [Buffer]

	mov	eax, ecx

	cld
	shr	ecx, 2
	rep	movsd
	mov	ecx, eax
	and	ecx, 3
	cmp	ecx, byte 0
	jz	@F

	rep	movsb
	
@@
	pop	ebx
	pop	edi
	pop	esi
	retn	12


VMP3D_Get_Version:
	mov	eax, 0100h
	clc
	retn


VMP3D_Get_IRQ:
	VxDCall	VDSPD_Get_IRQ
	retn


VMP3D_On_SoftIce_IRQ:
	pushad
	call	OnDspIrq
	popad
	clc
	retn


VMP3D_Need_SoftIce_IRQ:
	cmp	byte [IsPlaying], 0
	retn


VMP3D_Play:
	bts	dword [IsBusy], 0
	jc	@F

	pushad
	call	SetIndex		; eax: index
	jc	@F

	call	PlayMpegIndex

	btr	dword [IsBusy], 0

@@
	popad
	retn


VMP3D_Play_Next:
	bts	dword [IsBusy], 0
	jc	@F

	pushad
	call	SetIndexNext
	jc	@F

	call	PlayMpegIndex

	btr	dword [IsBusy], 0

@@
	popad
	retn


VMP3D_Play_Prev:
	bts	dword [IsBusy], 0
	jc	@F

	pushad
	call	SetIndexPrev
	jc	@F

	call	PlayMpegIndex

	btr	dword [IsBusy], 0

@@
	popad
	retn


VMP3D_Stop:
	pushad
	cmp	byte [IsPlaying], 0
	jz	@F

	bts	dword [IsBusy], 0
	jc	@F

	mov	eax, 1
	call	StopMpeg

	btr	dword [IsBusy], 0

@@
	popad
	clc
	retn


VMP3D_Pause:
	retn		; NOT READY YET

	pushad
	cmp	byte [IsPlaying], 0
	jz	@F

	VxDCall	VDSPD_Pause

@@
	popad
	retn


VMP3D_Resume:
	retn		; NOT READY YET

	pushad
	cmp	byte [IsPlaying], 0
	jz	@F

	VxDCall	VDSPD_Resume

@@
	popad
	retn
