; ; Command & Conquer Red Alert(tm) ; Copyright 2025 Electronic Arts Inc. ; ; This program is free software: you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation, either version 3 of the License, or ; (at your option) any later version. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with this program. If not, see . ; ;**************************************************************************** ;* ;* Copyright (c) 1994, HMI, INC. All Rights Reserved ;* ;*--------------------------------------------------------------------------- ;* ;* FILE ;* soscodec.asm ;* ;* DESCRIPTION ;* HMI SOS ADPCM compression/decompression. ;* ;* PROGRAMMER ;* Nick Skrepetos ;* Denzil E. Long, Jr. (Fixed bugs, rewrote for watcom) ;* Bill Petro (Added stereo support) ;* Jonathan Lanier ;* ;* DATE ;* Febuary 15, 1995 ;* ;* LAST MODIFIED ;* 08/07/95 [jdl] - Rewrote/optimized sosCODECDecompressData ;* ;*--------------------------------------------------------------------------- ;* ;* PUBLIC ;* ;**************************************************************************** IDEAL P386 MODEL USE32 FLAT LOCALS ?? DPMI_INTR equ 31h IF_LOCKED_PM_CODE equ 1h ; Locked PM code for DPMI. IF_LOCKED_PM_DATA equ 2h ; Locked PM code for DPMI. STRUC sCompInfo lpSource DD ? ;Compressed data pointer lpDest DD ? ;Uncompressed data pointer dwCompSize DD ? ;Compressed size dwUnCompSize DD ? ;Uncompressed size dwSampleIndex DD ? ;Index into sample dwPredicted DD ? ;Next predicted value dwDifference DD ? ;Difference from last sample wCodeBuf DW ? ;Holds 2 nibbles for decompression wCode DW ? ;Current 4 bit code wStep DW ? ;Step value in table wIndex DW ? ;Index into step table dwSampleIndex2 DD ? ;Index into sample dwPredicted2 DD ? ;Next predicted value dwDifference2 DD ? ;Difference from last sample wCodeBuf2 DW ? ;Holds 2 nibbles for decompression wCode2 DW ? ;Current 4 bit code wStep2 DW ? ;Step value in table wIndex2 DW ? ;Index into step table wBitSize DW ? ;Bit size for decompression wChannels DW ? ;number of channels ENDS sCompInfo DATASEG InitFlags DD 0 ; Flags to indicate what has been initialized. LABEL LockedDataStart BYTE ;* Index table for stepping into step table INCLUDE "difftb.inc" INCLUDE "indextb.inc" INCLUDE "nybbtb.inc" LABEL LockedDataEnd BYTE CODESEG LABEL LockedCodeStart BYTE ;**************************************************************************** ;* ;* NAME ;* sosCODECInitStream - Initialize compression stream. ;* ;* SYNOPSIS ;* sosCODECInitStream(CompInfo) ;* ;* void sosCODECInitStream(_SOS_COMPRESS_INFO *); ;* ;* FUNCTION ;* Initialize compression stream for compression and decompression. ;* ;* INPUTS ;* CompInfo - Compression information structure. ;* ;* RESULT ;* NONE ;* ;**************************************************************************** GLOBAL sosCODECInitStream:NEAR PROC sosCODECInitStream C NEAR ARG sSOSInfo:NEAR PTR mov eax,[sSOSInfo] mov [(sCompInfo eax).wIndex],0 ; starting index 0 mov [(sCompInfo eax).dwPredicted],0 ; no predicted value mov [(sCompInfo eax).wIndex2],0 ; starting index 0 mov [(sCompInfo eax).dwPredicted2],0 ; no predicted value ret ENDP sosCODECInitStream ;**************************************************************************** ;* ;* NAME ;* sosCODECDecompressData - Decompress audio data. ;* ;* SYNOPSIS ;* Size = sosCODECDecompressData(CompInfo, NumBytes) ;* ;* long sosCODECDecompressData(_SOS_COMPRESS_INFO *, long); ;* ;* FUNCTION ;* Decompress data from a 4:1 ADPCM compressed stream. The number of ;* bytes decompressed is returned. ;* ;* INPUTS ;* CompInfo - Compress information structure. ;* NumBytes - Number of bytes to decompress. ;* ;* RESULT ;* Size - Size of decompressed data. ;* ;* ;* NOTES ;* This routine has been optimized for pipelining on both ;* 486 and Pentium processors. Changing, inserting, or moving any ;* instructions will most likely slow down the code, in some cases by ;* as much as 20%. It can burst-decompress 16384 samples in about ;* 1940ęs on a Pentium 90Mhz, and about 3960ęs on a 486 66Mhz. ;* Instruction reordering could bring this down to below 1870ęs on ;* the Pentium, but this would cause a great degradation in 486 ;* performance. Since slow 486's are the reason this code was ;* written to be fast, it has been optimized for the Pentium only where ;* it would not degrade 486 performance. So, be careful when changing ;* ANY of this code, because it is very carefully balanced... ;**************************************************************************** GLOBAL C sosCODECDecompressData:NEAR PROC sosCODECDecompressData C NEAR ARG sSOSInfo:NEAR PTR ARG wBytes:DWORD push esi push edi push ebx push ecx push edx ;save all the regs mov ebx,[sSOSInfo] ;get base of sCompInfo struct mov cx,[(sCompInfo ebx).wBitSize] ;check the bit size mov dx,[(sCompInfo ebx).wChannels] ;check the number of channels ; ; ; Determine the correct routine to use for decoding ; (for now only ADPCM 4:1 Mono 16-bit is implemented) cmp cx,8 jne ??do16Bits ??do8Bits: cmp dx,2 jne ??not8Stereo ; jmp ??decomp8Stereo jmp ??byeBye ??not8Stereo: cmp dx,1 jne ??byeBye ; jmp decomp8Mono jmp ??byeBye ??do16Bits: cmp cx,16 jne ??byeBye cmp dx,2 jne ??not16Stereo ; jmp ??decomp16Stereo jmp ??byeBye ??not16Stereo: cmp dx,1 jne ??byeBye push ebp ; ; ; 16 bit ADPCM 4:1 Mono pre-loop initialization ??decomp16Mono: push ebx ;save struct base xor edx,edx ;clear index mov eax,[(sCompInfo ebx).dwPredicted] ;get last sample mov dx,[(sCompInfo ebx).wIndex] ;get last index value mov esi,[(sCompInfo ebx).lpSource] ;get source address mov edi,[(sCompInfo ebx).lpDest] ;get dest address mov ebp,[wBytes] ;get the number of dest. bytes cmp ebp,16 ;less than 16? (less than 8 samples) jl ??fixAlign16Mono0 ;if so, don't bother with alignment ; ; ; Check to see if we need to fix an alignment problem on the source buffer ; (non-aligned buffers are MUCH slower; if we're given a non-DWORD aligned ; source address, we do as many samples as needed to get to the nearest ; DWORD boundary, then finish the bulk as a DWORD-aligned decompress). mov ebx,esi ;get source address and ebx,03h ;check LSB jnz ??fixalign16Mono ;if non-zero, need to align for ;warp speed ??fixAlign16Mono0: push ebp ;save for later shr ebp,4 ;divide by 16 for 16-bit, ;because we do 8 nybbles per loop, ;and there are two samples per ;byte, so there are n/16 iterations ;required xor ebx,ebx ;clear our nybble index or ebp,ebp ;set flags for EBP jmp ??start16Mono ;start with test... don't go if ;we have zero bytes to do ??fixalign16Mono: jmp [DWORD PTR dwMono16AlignJmpTable+ebx*4] ;do non-aligned first align 4 ??fixAlign16Mono1: sub ebp,12 ;adjust # of dest. bytes push ebp ;save it shr ebp,4 ;divide by 16 to get samples/8 xor ebx,ebx ;clear our nybble index inc ebp ;adjust ebp for loop jmp ??finish16Mono6 ;borrow exit code to go through a ;piece of a loop align 4 ??fixAlign16Mono2: sub ebp,8 ;adjust # of dest. bytes push ebp ;save it shr ebp,4 ;divide by 16 to get samples/8 xor ebx,ebx ;clear our nybble index inc ebp ;adjust ebp for loop jmp ??finish16Mono4 ;borrow exit code to go through a ;piece of a loop align 4 ??fixAlign16Mono3: sub ebp,4 ;adjust # of dest. bytes push ebp ;save it shr ebp,4 ;divide by 16 to get samples/8 xor ebx,ebx ;clear our nybble index inc ebp ;adjust ebp for loop jmp ??finish16Mono2 ;borrow exit code to go through a ;piece of a loop ; "The Loop" ; ; Process 1st nybble align 4 ??loop16Mono: add eax,[dwDiffTable+edx*2] ;add difference to prev. sample mov dx,[wIndexTable+edx] ;adjust dx to next index base cmp eax,00007FFFh ;check for overflow jg ??fix1Smp16MonoO cmp eax,0FFFF8000h ;check for underflow jl ??fix1Smp16MonoU ??fixed1Smp16Mono: or dl,[BYTE PTR bNybbleTableHigh+ebx] ;adjust index for nybble mov [edi],ax ;save the sample ; ; ; Process 2nd nybble ??finish7Smp16Mono: mov bl,ch ;get next 2 nybbles in ebx add eax,[dwDiffTable+edx*2] ;add difference to prev. sample mov dx,[wIndexTable+edx] ;adjust dx to next index base cmp eax,00007FFFh ;check for overflow jg ??fix2Smp16MonoO cmp eax,0FFFF8000h ;check for underflow jl ??fix2Smp16MonoU ??fixed2Smp16Mono: or dl,[BYTE PTR bNybbleTableLow+ebx] ;adjust index for nybble mov [edi+02h],ax ;save the sample shr ecx,16 ;move top four nybbles into bottom ; ; ; Process 3rd nybble ??finish6Smp16Mono: add eax,[dwDiffTable+edx*2] ;add difference to prev. sample mov dx,[wIndexTable+edx] ;adjust dx to next index base cmp eax,00007FFFh ;check for overflow jg ??fix3Smp16MonoO cmp eax,0FFFF8000h ;check for underflow jl ??fix3Smp16MonoU ??fixed3Smp16Mono: or dl,[BYTE PTR bNybbleTableHigh+ebx] ;adjust index for nybble mov [edi+04h],ax ;save the sample ; ; ; Process 4th nybble ??finish5Smp16Mono: mov bl,cl add eax,[dwDiffTable+edx*2] ;add difference to prev. sample mov dx,[wIndexTable+edx] ;adjust dx to next index base cmp eax,00007FFFh ;check for overflow jg ??fix4Smp16MonoO cmp eax,0FFFF8000h ;check for underflow jl ??fix4Smp16MonoU ??fixed4Smp16Mono: or dl,[BYTE PTR bNybbleTableLow+ebx] ;adjust index for nybble mov [edi+06h],ax ;save the sample ; ; ; Process 5th nybble ??finish4Smp16Mono: add eax,[dwDiffTable+edx*2] ;add difference to prev. sample mov dx,[wIndexTable+edx] ;adjust dx to next index base cmp eax,00007FFFh ;check for overflow jg ??fix5Smp16MonoO cmp eax,0FFFF8000h ;check for underflow jl ??fix5Smp16MonoU ??fixed5Smp16Mono: or dl,[BYTE PTR bNybbleTableHigh+ebx] ;adjust index for nybble mov [edi+08h],ax ;save the sample ; ; ; Process 6th nybble ??finish3Smp16Mono: mov bl,ch add eax,[dwDiffTable+edx*2] ;add difference to prev. sample mov dx,[wIndexTable+edx] ;adjust dx to next index base cmp eax,00007FFFh ;check for overflow jg ??fix6Smp16MonoO cmp eax,0FFFF8000h ;check for underflow jl ??fix6Smp16MonoU ??fixed6Smp16Mono: or dl,[BYTE PTR bNybbleTableLow+ebx] ;adjust index for nybble mov [edi+0Ah],ax ;save the sample ; ; ; Process 7th nybble ??finish2Smp16Mono: add eax,[dwDiffTable+edx*2] ;add difference to prev. sample mov dx,[wIndexTable+edx] ;adjust dx to next index base cmp eax,00007FFFh ;check for overflow jg ??fix7Smp16MonoO cmp eax,0FFFF8000h ;check for underflow jl ??fix7Smp16MonoU ??fixed7Smp16Mono: or dl,[BYTE PTR bNybbleTableHigh+ebx] ;adjust index for nybble mov [edi+0Ch],ax ;save the sample ; ; ; Process 8th nybble ??finish1Smp16Mono: add eax,[dwDiffTable+edx*2] ;add difference to prev. sample mov dx,[wIndexTable+edx] ;adjust dx to next index base cmp eax,00007FFFh ;check for overflow jg ??fix8Smp16MonoO cmp eax,0FFFF8000h ;check for underflow jl ??fix8Smp16MonoU ; ; ; Loop cleanup for next pass ??fixed8Smp16Mono: mov [edi+0Eh],ax ;save the sample add esi,04h ;bump esi to point to next longword add edi,10h ;incr. the destination buffer ptr dec ebp ;count down the number of samples/8 ??start16Mono: jng ??cleanup16Mono ;if done, clean up mov ecx,[esi] ;get 4 nybbles in one whack (whee!) mov bl,cl ;get next 2 nybbles in ebx or dl,[BYTE PTR bNybbleTableLow+ebx] ;adjust index for nybble jmp ??loop16Mono ;loop until done ??cleanup16Mono: jnz ??done16Mono ;if ebp is non-zero, we're DONE ;if exactly zero, finish the tail-end ;of the conversion (may be a non- ;multiple of 8 nybbles) ; ; ; Loop cleanup for last (incomplete) pass pop ecx ;restore # of words shr ecx,1 ;divide by two to get samples and ecx,07h ;get # of samples we missed jmp [DWORD PTR dwMono16JmpTable+ecx*4] ;go finish the job... ; ; ; Structure cleanup ??done16Mono: pop ebx ;restore struct base pop ebp ;restore stack frame pointer mov [(sCompInfo ebx).dwPredicted],eax ;save last sample mov [(sCompInfo ebx).wIndex],dx ;save last index value mov eax,[wBytes] ;get # of bytes we did ??byeBye: pop edx ;restore all the regs pop ecx pop ebx pop edi pop esi ret ; ; ; Jumps for -32768/+32767 bounds check go to these vvvv align 4 ??fix1Smp16MonoO: mov eax,00007FFFh ;Overflow - truncate to +32767 jmp ??fixed1Smp16Mono ;go back align 4 ??fix1Smp16MonoU: mov eax,0FFFF8000h ;Underflow - truncate to -32768 jmp ??fixed1Smp16Mono ;go back align 4 ??fix2Smp16MonoO: mov eax,00007FFFh ;Overflow - truncate to +32767 jmp ??fixed2Smp16Mono ;go back align 4 ??fix2Smp16MonoU: mov eax,0FFFF8000h ;Underflow - truncate to -32768 jmp ??fixed2Smp16Mono ;go back align 4 ??fix3Smp16MonoO: mov eax,00007FFFh ;Overflow - truncate to +32767 jmp ??fixed3Smp16Mono ;go back align 4 ??fix3Smp16MonoU: mov eax,0FFFF8000h ;Underflow - truncate to -32768 jmp ??fixed3Smp16Mono ;go back align 4 ??fix4Smp16MonoO: mov eax,00007FFFh ;Overflow - truncate to +32767 jmp ??fixed4Smp16Mono ;go back align 4 ??fix4Smp16MonoU: mov eax,0FFFF8000h ;Underflow - truncate to -32768 jmp ??fixed4Smp16Mono ;go back align 4 ??fix5Smp16MonoO: mov eax,00007FFFh ;Overflow - truncate to +32767 jmp ??fixed5Smp16Mono ;go back align 4 ??fix5Smp16MonoU: mov eax,0FFFF8000h ;Underflow - truncate to -32768 jmp ??fixed5Smp16Mono ;go back align 4 ??fix6Smp16MonoO: mov eax,00007FFFh ;Overflow - truncate to +32767 jmp ??fixed6Smp16Mono ;go back align 4 ??fix6Smp16MonoU: mov eax,0FFFF8000h ;Underflow - truncate to -32768 jmp ??fixed6Smp16Mono ;go back align 4 ??fix7Smp16MonoO: mov eax,00007FFFh ;Overflow - truncate to +32767 jmp ??fixed7Smp16Mono ;go back align 4 ??fix7Smp16MonoU: mov eax,0FFFF8000h ;Underflow - truncate to -32768 jmp ??fixed7Smp16Mono ;go back align 4 ??fix8Smp16MonoO: mov eax,00007FFFh ;Overflow - truncate to +32767 jmp ??fixed8Smp16Mono ;go back align 4 ??fix8Smp16MonoU: mov eax,0FFFF8000h ;Underflow - truncate to -32768 jmp ??fixed8Smp16Mono ;go back ; ; ; Jump tables for cleanup after loop unroll point to these vvvv align 4 ??finish16Mono1: xor ecx,ecx ;clear nybble bucket mov ch,[esi] ;get 1 nybble (1 byte) shl ch,4 ;move it over mov bl,ch ;get nybble in ebx sub edi,0Eh ;back edi up or dl,[BYTE PTR bNybbleTableHigh+ebx] ;adjust index for nybble jmp ??finish1Smp16Mono ;go finish it align 4 ??finish16Mono2: xor ecx,ecx ;clear nybble bucket mov ch,[esi] ;get 2 nybbles (1 byte) mov bl,ch ;get nybbles in ebx sub edi,0Ch ;back edi up sub esi,3 ;adjust esi (used for dword aligning) or dl,[BYTE PTR bNybbleTableLow+ebx] ;adjust index for nybble jmp ??finish2Smp16Mono ;go finish it align 4 ??finish16Mono3: xor ecx,ecx ;clear nybble bucket mov cx,[esi] ;get 3 nybbles (2 bytes) shl cx,4 ;move it over mov bl,cl ;get nybbles in ebx sub edi,0Ah ;back edi up or dl,[BYTE PTR bNybbleTableHigh+ebx] ;adjust index for nybble jmp ??finish3Smp16Mono ;go finish it align 4 ??finish16Mono4: xor ecx,ecx ;clear nybble bucket mov cx,[esi] ;get 4 nybbles (2 bytes) mov bl,cl ;get nybbles in ebx sub edi,08h ;back edi up sub esi,2 ;adjust esi (used for dword aligning) or dl,[BYTE PTR bNybbleTableLow+ebx] ;adjust index for nybble jmp ??finish4Smp16Mono ;go finish it align 4 ??finish16Mono5: xor ecx,ecx ;clear nybble bucket mov cl,[esi+2] ;get 1 nybble (1 byte) shl ecx,16 ;shift it over mov cx,[esi] ;get 4 nybbles (2 bytes) mov bl,cl ;get nybbles in ebx shr ecx,4 ;move it over shl bl,4 ;move it over sub edi,06h ;back edi up or dl,[BYTE PTR bNybbleTableHigh+ebx] ;adjust index for nybble jmp ??finish5Smp16Mono ;go finish it align 4 ??finish16Mono6: xor ecx,ecx ;clear nybble bucket mov cl,[esi+2] ;get 2 nybbles (1 byte) shl ecx,16 ;move it over mov cx,[esi] ;get 4 nybbles (2 bytes) mov bl,cl ;get nybbles in ebx shr ecx,8 ;move it over sub esi,1 ;adjust esi (used for dword aligning) sub edi,04h ;back edi up or dl,[BYTE PTR bNybbleTableLow+ebx] ;adjust index for nybble jmp ??finish6Smp16Mono ;go finish it align 4 ??finish16Mono7: xor ecx,ecx ;clear nybble bucket mov ecx,[esi] ;get 7 nybbles (4 bytes) shl ecx,4 ;move it over mov bl,cl ;get nybbles in ebx sub edi,02h ;back edi up or dl,[BYTE PTR bNybbleTableHigh+ebx] ;adjust index for nybble jmp ??finish7Smp16Mono ;go finish it ; ; ; Jump Tables align 4 dwMono16JmpTable DD ??done16Mono DD ??finish16Mono1 DD ??finish16Mono2 DD ??finish16Mono3 DD ??finish16Mono4 DD ??finish16Mono5 DD ??finish16Mono6 DD ??finish16Mono7 align 4 dwMono16AlignJmpTable DD ??fixAlign16Mono0 DD ??fixAlign16Mono1 DD ??fixAlign16Mono2 DD ??fixAlign16Mono3 ENDP sosCODECDecompressData LABEL LockedCodeEnd BYTE ;*************************************************************************** ;* sosCODEC_LOCK -- locks the JLB audio decompression code * ;* * ;* INPUT: none * ;* * ;* OUTPUT: BOOL true is lock sucessful, false otherwise * ;* * ;* PROTO: BOOL sosCODEC_Lock(void); * ;* * ;* HISTORY: * ;* 06/26/1995 PWG : Created. * ;*=========================================================================* GLOBAL sosCODEC_Lock:NEAR PROC sosCODEC_Lock C NEAR USES ebx ecx edx esi edi ; ; Lock the code that is used by the sos decompression method. ; mov eax,0600h ; function number. mov ecx,OFFSET LockedCodeStart ; ecx must have start of memory. mov edi,OFFSET LockedCodeEnd ; edi will have size of region in bytes. shld ebx,ecx,16 sub edi, ecx shld esi,edi,16 int DPMI_INTR ; do call. jc ??error or [InitFlags], IF_LOCKED_PM_CODE ; ; Lock the data used by the sos decompression method. ; mov eax,0600h ; function number. mov ecx,OFFSET LockedDataStart ; ecx must have start of memory. mov edi,OFFSET LockedDataEnd ; edi will have size of region in bytes. shld ebx,ecx,16 sub edi, ecx shld esi,edi,16 int DPMI_INTR ; do call. jc ??error ; eax = 8 if mem err, eax = 9 if invalid mem region. or [InitFlags], IF_LOCKED_PM_DATA mov eax,1 jmp ??exit ??error: xor eax,eax ??exit: ret ENDP sosCODEC_Lock ;*************************************************************************** ;* DECOMPRESS_FRAME_UNLOCK -- Unlocks the JLB audio compression code * ;* * ;* INPUT: none * ;* * ;* OUTPUT: BOOL true is unlock sucessful, false otherwise * ;* * ;* PROTO: BOOL sosCODEC_Unlock(void); * ;* * ;* HISTORY: * ;* 06/26/1995 PWG : Created. * ;*=========================================================================* GLOBAL sosCODEC_Unlock:NEAR PROC sosCODEC_Unlock C NEAR USES ebx ecx edx esi edi test [InitFlags],IF_LOCKED_PM_CODE jz ??code_not_locked mov eax , 0601h mov ecx,OFFSET LockedCodeStart ; ecx must have start of memory. mov edi,OFFSET LockedCodeEnd ; edx will have size of region in bytes. sub edi,ecx ; - figure size. shld ebx , ecx , 16 shld esi , edi , 16 int DPMI_INTR ; do call. jc ??error ??code_not_locked: test [InitFlags],IF_LOCKED_PM_DATA jz ??data_not_locked mov ax,0601h ; set es to descriptor of data. mov ecx,OFFSET LockedDataStart ; ecx must have start of memory. mov edi,OFFSET LockedDataEnd ; edx will have size of region in bytes. sub edi,ecx ; - figure size. shld ebx , ecx , 16 shld esi , edi , 16 int DPMI_INTR ; do call. jc ??error ; eax = 8 if mem err, eax = 9 if invalid mem region. ??data_not_locked: mov [InitFlags],0 mov eax,1 jmp ??exit ??error: xor eax,eax ??exit: ret ENDP sosCODEC_Unlock END