%include "util.mac"
%include "icedump.inc"
%include "vxdn.inc"
%include "wiat.inc"


global Parse_CDPlayer
global Service_CDPlayer


extern sdata
extern Parser.error
extern Parser.errorMsg
extern Error_PMR0
extern SetCB
extern ParseExpression


bits 32


;-------------------------------------------------------------------------------
; CD [<track>]
;-------------------------------------------------------------------------------
segment _LTEXT
Parse_CDPlayer:
	mov	edi,Error_PMR0
	mov	ebp,[dClient_CS]
	test	byte [ebp],3		; is client in ring-0?
	jz	near Parser.errorMsg

	call	[pSkipWhiteSpace]	; skip to track#
	jnz	.GetTrack

	mov	al,-1			; issue STOP
	jmp	short .SetCB

.GetTrack:
	mov	edi,.Error_Track
	call	ParseExpression		; parse <track>
	jc	near Parser.errorMsg

.SetCB:
	mov	ebp,[dClient_ECX]	; store track number
	mov	[ebp],eax

	mov	ebp,[dClient_EAX]
	movb	dword [ebp],SERVICE_CDPLAYER

	call	SetCB

	mov	ebp,[fExecuteMoreCommands]	; set internal Winice flag to 0
	mov	byte [ebp],0

	popad
	retn


segment _LDATA
.Error_Track	db 'could not parse track.',0


;-------------------------------------------------------------------------------
; this is the actual CD Player code that's executed in ring-0.
;	EBP = CRS
;-------------------------------------------------------------------------------
segment _LTEXT
Service_CDPlayer:
	mov	eax,[ebp+CRS.ECX]	; track number
	mov	[.Track],eax

	mov	[.CurES],es

	VMMCall	Begin_Nest_V86_Exec

; check for cdrom
	mov	[ebp+CRS.EAX],word 0x1500
	movb	dword [ebp+CRS.EBX],0
	movb	eax,0x2F
	VMMCall	Exec_Int
	movzx	ebx,word [ebp+CRS.EBX]

	mov	edi,.NoCdRom
	or	ebx,ebx
	jz	.PrintError

	mov	eax,[ebp+CRS.ECX]
	mov	[.CdRom],eax

	call	.GetDiskInfo
	test	eax,eax
	jnz	.Error

	mov	ecx,[.Track]		; track #

	cmp	cl,-1
	jne	.PlayTracks

; track == -1, it's a STOP
	call	.Stop
	jmp	short .ExitNested

.PlayTracks:
; check if this track exists on CD
	cmp	cl,bl
	jb	.BadTrack

	cmp	cl,bh
	jbe	.TrackOk

.BadTrack:
	mov	edi,.ValidMsg
	movzx	eax,bh
	movzx	ebx,bl
	jmp	short .PrintError

.TrackOk:
	mov	al,cl
	call	.GetTrackInfo
	test	eax,eax
	jnz	.Error

	mov	eax,ebx
	call	.Play
	test	eax,eax
	jnz	.Error

	jmp	short .ExitNested

.Error:
	mov	edi,.CdError

.PrintError:
	push	edi
	VMMCall	_Trace_Out_Service

.ExitNested:
	VMMCall	End_Nest_Exec
	popfd
	popad
	retn


segment _LDATA
	align 4
		dw 0	; dummy
.CurES		dw 0
.DiscEnd	dd 0
.CdRom		dd 0	; CD-Rom drive letter code
.Track		dd 0
.NoCdRom	db 'CD-Rom unavailable',CRLF0
.CdError	db 'CD-Rom error #EAX',CRLF0
.ValidMsg	db 'Valid tracks are #EBX to #EAX',CRLF0
;.TestMsg	db 'Someone wants to hear track #ECX. Amazing, SoftICE is still alive =)',CRLF0


segment _LTEXT
.Stop:
	movb	ecx,CdReq_size
	call	.AllocV86
	jc	.AllocError

	mov	ebx,133			; STOP AUDIO
	xor	ecx,ecx
	call	.CdCommand

	call	.FreeV86
	retn

; EAX = track start
.Play:
	movb	ecx,CdReq_size+CdPlayReq_size
	call	.AllocV86
	jc	.AllocError

	mov	ebx,132			; PLAY AUDIO
	movb	ecx,CdPlayReq_size
	mov	[edi+CdReq_size+CdPlayReq.AddrMode],byte 0
	mov	[edi+CdReq_size+CdPlayReq.Sector],eax
	sub	eax,[.DiscEnd]
	neg	eax
	mov	[edi+CdReq_size+CdPlayReq.Count],eax
	call	.CdCommand

	call	.FreeV86
	retn

; AL = track#
; returns track start in EBX
.GetTrackInfo:
	movb	ecx,CdIoctlReq_size+CdTrackInfo_size
	call	.AllocV86
	jc	.AllocError

	movb	ebx,0x0B		; IOCTLI/TRACK_INFO
	movb	ecx,CdTrackInfo_size
	mov	[edi+CdIoctlReq_size+CdTrackInfo.Track],al
	call	.CdIoCtl

	mov	ebx,[edi+CdIoctlReq_size+CdTrackInfo.Start]
	call	.RedBook2HSG

	call	.FreeV86
	retn

.AllocError:
	mov	al,0xFF
	jmp	.DrvErr

; returns Lo-Hi track #s in BX
.GetDiskInfo:
	movb	ecx,CdIoctlReq_size+CdDiskInfo_size
	call	.AllocV86
	jc	.AllocError

	movb	ebx,0x0A		; IOCTLI/AUDIO_INFO
	movb	ecx,CdDiskInfo_size
	call	.CdIoCtl

	mov	ebx,[edi+CdIoctlReq_size+CdDiskInfo.LeadOut]
	call	.RedBook2HSG
	mov	[.DiscEnd],ebx

	mov	ebx,[edi+CdIoctlReq_size+CdDiskInfo.TrackLo]

	call	.FreeV86
	retn

; BX = driver command, ECX = extra request data length
.CdCommand:
	mov	edx,ecx
	movb	ecx,CdReq_size		; 0-init the req structure
	push	edi
	xor	al,al
	rep	stosb
	pop	edi

	add	edx,byte CdReq_size
	mov	[edi+CdReq.Len],dl
	mov	[edi+CdReq.Cmd],bx

	jmp	short .CdCallExt

; BX = I/O + control code, ECX = control block data length
.CdIoCtl:
	mov	[edi+CdIoctlReq.CtrlCode],bl
	mov	edx,ecx
	movb	ecx,CdIoctlReq_size	; 0-init the req structure
	push	edi
	xor	al,al
	rep	stosb
	pop	edi

; setup the IOCTL req structure
	movb	eax,3			; IOCTLI
	test	bh,0x80
	jz	.notO

	movb	eax,12			; IOCTLO

.notO:
	mov	[edi+CdIoctlReq.Cmd],ax
	mov	[edi+CdIoctlReq.Len],byte CdIoctlReq_size
	mov	[edi+CdIoctlReq.XferSize],dx

; setup IOCTL transfer address
	lea	eax,[edi+CdIoctlReq_size]
	shl	eax,12
	shr	ax,12
	mov	[edi+CdIoctlReq.XferOfs],eax

.CdCallExt:
; setup es:bx pair
	mov	eax,edi
	ror	eax,4
	mov	[ebp+CRS.ES],ax
	shr	eax,28
	mov	[ebp+CRS.EBX],ax

	mov	[ebp+CRS.EAX],word 0x1510
	mov	eax,[.CdRom]
	mov	[ebp+CRS.ECX],eax
	movb	eax,0x2F

	push	es
	mov	es,[.CurES]
	VMMCall	Exec_Int
	pop	es

	movzx	eax,word [ebp+CRS.EAX]
	mov	ebx,[ebp+CRS.EFlags]		; test CF
	and	ebx,byte 1
	jnz	.DrvNotCalled			; there was CF

	movzx	eax,word [edi+CdReq.Status]
	test	ah,0x80
	jnz	.DrvErr

	xor	eax,eax

.DrvErr:
	movzx	eax,al

.DrvNotCalled:
	retn

; !!! always call Free after Alloc if alloc succeeded
.AllocV86:
	push	eax
	VMMCall	Get_Cur_VM_Handle
	clc
	VxDCall	V86MMGR_Allocate_Buffer
	mov	[.BufSizeV86],ecx
	jc	._allocerr

; calc linear v86 addr
	mov	eax,edi
	shr	edi,16
	shl	edi,4
	movzx	eax,ax
	add	edi,eax
	VxDCall	V86MMGR_Get_VM_Flat_Sel
	mov	es,eax

._allocerr:
	pop	eax
	retn

.FreeV86:
	push	ebx
	mov	es,[.CurES]
	mov	ecx,[.BufSizeV86]
	VMMCall	Get_Cur_VM_Handle
	clc
	VxDCall	V86MMGR_Free_Buffer
	pop	ebx
	retn


segment _LDATA
	align 4
.BufSizeV86	dd 0


; HSG = (Min * 60 + Sec) * 75 + Frame - 150
segment _LTEXT
.RedBook2HSG:
	mov	edx,ebx

	shr	ebx,16
	imul	ebx,byte 60

	movzx	ecx,dh
	add	ebx,ecx
	imul	ebx,byte 75

	movzx	ecx,dl
	lea	ebx,[ebx+ecx-150]
	retn
