Decouple gameplay from presentation and preserve signed movement fractions. Separate cinematic camera ownership with safe body handoff and transactional current-room restarts. Resample input latches on focus regain, discard modal timing debt, and attach cinematic lighting to the view to match editor preview. Verify editor 137/137 and ASM 288/288, cinematic traces, RGB bake and IPC framebuffer parity without changing project assets.
602 lines
14 KiB
PHP
602 lines
14 KiB
PHP
; Timed 60 Hz cutscene track. Strings are bounded CP1251 data, never pointers.
|
|
.code
|
|
|
|
ValidateSoundFilename PROC uses ebx ecx esi, namePtr:DWORD
|
|
mov esi, namePtr
|
|
xor ecx, ecx
|
|
cs_sound_character:
|
|
mov al, [esi+ecx]
|
|
test al, al
|
|
jz cs_sound_end
|
|
cmp al, 32
|
|
jb cs_sound_invalid
|
|
cmp al, 127
|
|
jae cs_sound_invalid
|
|
cmp al, 92
|
|
je cs_sound_invalid
|
|
cmp al, '/'
|
|
je cs_sound_invalid
|
|
cmp al, ':'
|
|
je cs_sound_invalid
|
|
cmp al, '*'
|
|
je cs_sound_invalid
|
|
cmp al, '?'
|
|
je cs_sound_invalid
|
|
cmp al, 34
|
|
je cs_sound_invalid
|
|
cmp al, '<'
|
|
je cs_sound_invalid
|
|
cmp al, '>'
|
|
je cs_sound_invalid
|
|
cmp al, '|'
|
|
je cs_sound_invalid
|
|
inc ecx
|
|
cmp ecx, 64
|
|
jb cs_sound_character
|
|
cs_sound_invalid:
|
|
xor eax, eax
|
|
ret
|
|
cs_sound_end:
|
|
cmp ecx, 5
|
|
jb cs_sound_invalid
|
|
mov eax, [esi+ecx-4]
|
|
or eax, 20202000h
|
|
cmp eax, 7661772Eh
|
|
jne cs_sound_invalid
|
|
mov eax, 1
|
|
ret
|
|
ValidateSoundFilename ENDP
|
|
|
|
ValidateStagedCutscene PROC uses ebx ecx edx esi edi, eventCount:DWORD, stringCount:DWORD, flags:DWORD, cells:DWORD, transitions:DWORD
|
|
LOCAL previous:DWORD, eventIndex:DWORD
|
|
cmp eventCount, MAX_CUTSCENE_EVENTS
|
|
ja csv_invalid
|
|
cmp stringCount, MAX_CUTSCENE_STRINGS
|
|
ja csv_invalid
|
|
mov eax, flags
|
|
and eax, 0FFFFFFFEh
|
|
jnz csv_invalid
|
|
mov esi, OFFSET stagedCutsceneStrings
|
|
mov edx, stringCount
|
|
csv_next_string:
|
|
test edx, edx
|
|
jz csv_events
|
|
xor ecx, ecx
|
|
csv_string_char:
|
|
mov al, [esi+ecx]
|
|
test al, al
|
|
jz csv_string_end
|
|
cmp al, 10
|
|
je csv_string_advance
|
|
cmp al, 32
|
|
jb csv_invalid
|
|
csv_string_advance:
|
|
inc ecx
|
|
cmp ecx, CUTSCENE_STRING_BYTES
|
|
jb csv_string_char
|
|
jmp csv_invalid
|
|
csv_string_end:
|
|
add esi, CUTSCENE_STRING_BYTES
|
|
dec edx
|
|
jmp csv_next_string
|
|
csv_events:
|
|
mov esi, OFFSET stagedCutsceneEvents
|
|
mov previous, 0
|
|
mov eventIndex, 0
|
|
csv_event:
|
|
mov eax, eventIndex
|
|
cmp eax, eventCount
|
|
jae csv_finished
|
|
movzx eax, [esi].CutsceneEvent.tickTimestamp
|
|
cmp eax, previous
|
|
jb csv_invalid
|
|
mov previous, eax
|
|
movzx eax, [esi].CutsceneEvent.opCode
|
|
cmp eax, OP_CHANGE_SUBLEVEL
|
|
ja csv_invalid
|
|
movzx edx, [esi].CutsceneEvent.param8
|
|
cmp eax, OP_PLAY_SOUND
|
|
je csv_sound_flags
|
|
test edx, edx
|
|
jnz csv_invalid
|
|
jmp csv_dispatch
|
|
csv_sound_flags:
|
|
cmp edx, 1
|
|
ja csv_invalid
|
|
csv_dispatch:
|
|
cmp eax, OP_END
|
|
je csv_end
|
|
cmp eax, OP_WAIT
|
|
je csv_wait
|
|
cmp eax, OP_MOVE_CAM_TO
|
|
je csv_move
|
|
cmp eax, OP_PAN_CAM_TO
|
|
je csv_pan
|
|
cmp eax, OP_FADE_SCREEN
|
|
je csv_fade
|
|
cmp eax, OP_SHOW_SUBTITLE
|
|
je csv_text
|
|
cmp eax, OP_PLAY_SOUND
|
|
je csv_sound
|
|
cmp eax, OP_CHANGE_SUBLEVEL
|
|
je csv_transition
|
|
jmp csv_next
|
|
csv_end:
|
|
mov eax, eventIndex
|
|
inc eax
|
|
cmp eax, eventCount
|
|
jne csv_invalid
|
|
jmp csv_next
|
|
csv_wait:
|
|
mov eax, [esi].CutsceneEvent.paramA
|
|
cmp eax, CUTSCENE_MAX_DURATION
|
|
ja csv_invalid
|
|
jmp csv_next
|
|
csv_move:
|
|
mov eax, [esi].CutsceneEvent.paramA
|
|
cmp eax, MAP_W*CELL_SIZE
|
|
jae csv_invalid
|
|
mov ebx, [esi].CutsceneEvent.paramB
|
|
cmp ebx, MAP_H*CELL_SIZE
|
|
jae csv_invalid
|
|
shr eax, CELL_SHIFT
|
|
shr ebx, CELL_SHIFT
|
|
shl ebx, 5
|
|
add ebx, eax
|
|
add ebx, cells
|
|
mov al, [ebx]
|
|
cmp al, CELL_EMPTY
|
|
je csv_move_duration
|
|
cmp al, CELL_DOOR_OPEN
|
|
je csv_move_duration
|
|
cmp al, CELL_PORTAL
|
|
jne csv_invalid
|
|
csv_move_duration:
|
|
mov eax, [esi].CutsceneEvent.dataId
|
|
cmp eax, CUTSCENE_MAX_DURATION
|
|
ja csv_invalid
|
|
jmp csv_next
|
|
csv_pan:
|
|
cmp [esi].CutsceneEvent.paramA, ANGLE_MASK
|
|
ja csv_invalid
|
|
jmp csv_duration
|
|
csv_fade:
|
|
cmp [esi].CutsceneEvent.paramA, 16
|
|
ja csv_invalid
|
|
jmp csv_duration
|
|
csv_text:
|
|
mov eax, [esi].CutsceneEvent.paramA
|
|
cmp eax, stringCount
|
|
jae csv_invalid
|
|
csv_duration:
|
|
cmp [esi].CutsceneEvent.paramB, CUTSCENE_MAX_DURATION
|
|
ja csv_invalid
|
|
jmp csv_next
|
|
csv_sound:
|
|
mov eax, [esi].CutsceneEvent.paramA
|
|
cmp eax, -1
|
|
je csv_next
|
|
cmp eax, stringCount
|
|
jae csv_invalid
|
|
imul eax, CUTSCENE_STRING_BYTES
|
|
add eax, OFFSET stagedCutsceneStrings
|
|
invoke ValidateSoundFilename, eax
|
|
test eax, eax
|
|
jz csv_invalid
|
|
jmp csv_next
|
|
csv_transition:
|
|
mov eax, [esi].CutsceneEvent.paramA
|
|
cmp eax, transitions
|
|
jae csv_invalid
|
|
csv_next:
|
|
movzx eax, [esi].CutsceneEvent.opCode
|
|
xor ebx, ebx
|
|
cmp eax, OP_MOVE_CAM_TO
|
|
jne csv_span_not_move
|
|
mov ebx, [esi].CutsceneEvent.dataId
|
|
jmp csv_check_span
|
|
csv_span_not_move:
|
|
cmp eax, OP_WAIT
|
|
jne csv_span_not_wait
|
|
mov ebx, [esi].CutsceneEvent.paramA
|
|
jmp csv_check_span
|
|
csv_span_not_wait:
|
|
cmp eax, OP_PAN_CAM_TO
|
|
je csv_span_param_b
|
|
cmp eax, OP_FADE_SCREEN
|
|
je csv_span_param_b
|
|
cmp eax, OP_SHOW_SUBTITLE
|
|
jne csv_check_span
|
|
csv_span_param_b:
|
|
mov ebx, [esi].CutsceneEvent.paramB
|
|
csv_check_span:
|
|
movzx eax, [esi].CutsceneEvent.tickTimestamp
|
|
add eax, ebx
|
|
cmp eax, 65535
|
|
ja csv_invalid
|
|
add esi, sizeof CutsceneEvent
|
|
inc eventIndex
|
|
jmp csv_event
|
|
csv_finished:
|
|
cmp eventCount, 0
|
|
je csv_valid
|
|
cmp BYTE PTR [esi-sizeof CutsceneEvent+CutsceneEvent.opCode], OP_END
|
|
jne csv_invalid
|
|
csv_valid:
|
|
mov eax, 1
|
|
ret
|
|
csv_invalid:
|
|
xor eax, eax
|
|
ret
|
|
ValidateStagedCutscene ENDP
|
|
|
|
StopCutscene PROC uses eax ebx
|
|
cmp isCutsceneActive, 0
|
|
je cs_release_done
|
|
invoke CanOccupyPlayer, csCameraX, csCameraY
|
|
test eax, eax
|
|
jz cs_release_done
|
|
mov eax, csCameraX
|
|
mov playerX, eax
|
|
mov eax, csCameraY
|
|
mov playerY, eax
|
|
mov eax, csCameraAngle
|
|
mov playerAngle, eax
|
|
cs_release_done:
|
|
call ResetMovementFractions
|
|
mov isCutsceneActive, 0
|
|
mov cutscenePending, 0
|
|
mov cutsceneSubtitle, -1
|
|
mov cutsceneSubtitleUntil, 0
|
|
mov cutsceneFade, 0
|
|
mov csMoveDuration, 0
|
|
mov csAngleDuration, 0
|
|
mov csFadeDuration, 0
|
|
mov cutsceneWait, 0
|
|
mov eax, playerY
|
|
sar eax, CELL_SHIFT
|
|
shl eax, 5
|
|
mov ebx, playerX
|
|
sar ebx, CELL_SHIFT
|
|
add eax, ebx
|
|
mov portalLatchCell, eax
|
|
invoke PlaySoundA, 0, 0, 0
|
|
ret
|
|
StopCutscene ENDP
|
|
|
|
StartCutscene PROC
|
|
mov cutscenePending, 0
|
|
cmp cutsceneCount, 0
|
|
je cs_start_empty
|
|
call StopCutscene
|
|
mov eax, playerX
|
|
mov csCameraX, eax
|
|
mov eax, playerY
|
|
mov csCameraY, eax
|
|
mov eax, playerAngle
|
|
mov csCameraAngle, eax
|
|
mov cutsceneTick, 0
|
|
mov cutsceneIndex, 0
|
|
mov isCutsceneActive, 1
|
|
mov eax, 1
|
|
ret
|
|
cs_start_empty:
|
|
xor eax, eax
|
|
ret
|
|
StartCutscene ENDP
|
|
|
|
UpdateCutscene PROC uses ebx ecx edx esi edi
|
|
LOCAL transitionFade:DWORD
|
|
cmp isCutsceneActive, 0
|
|
je cs_update_done
|
|
cmp csMoveDuration, 0
|
|
je cs_step_angle
|
|
inc csMoveElapsed
|
|
mov eax, csMoveToX
|
|
sub eax, csMoveFromX
|
|
imul csMoveElapsed
|
|
idiv csMoveDuration
|
|
add eax, csMoveFromX
|
|
mov csCameraX, eax
|
|
mov eax, csMoveToY
|
|
sub eax, csMoveFromY
|
|
imul csMoveElapsed
|
|
idiv csMoveDuration
|
|
add eax, csMoveFromY
|
|
mov csCameraY, eax
|
|
mov eax, csMoveElapsed
|
|
cmp eax, csMoveDuration
|
|
jb cs_step_angle
|
|
mov csMoveDuration, 0
|
|
cs_step_angle:
|
|
cmp csAngleDuration, 0
|
|
je cs_step_fade
|
|
inc csAngleElapsed
|
|
mov eax, csAngleDelta
|
|
imul csAngleElapsed
|
|
idiv csAngleDuration
|
|
add eax, csAngleFrom
|
|
and eax, ANGLE_MASK
|
|
mov csCameraAngle, eax
|
|
mov eax, csAngleElapsed
|
|
cmp eax, csAngleDuration
|
|
jb cs_step_fade
|
|
mov csAngleDuration, 0
|
|
cs_step_fade:
|
|
cmp csFadeDuration, 0
|
|
je cs_subtitle_expiry
|
|
inc csFadeElapsed
|
|
mov eax, csFadeTo
|
|
sub eax, csFadeFrom
|
|
imul csFadeElapsed
|
|
idiv csFadeDuration
|
|
add eax, csFadeFrom
|
|
mov cutsceneFade, eax
|
|
mov eax, csFadeElapsed
|
|
cmp eax, csFadeDuration
|
|
jb cs_subtitle_expiry
|
|
mov csFadeDuration, 0
|
|
cs_subtitle_expiry:
|
|
cmp cutsceneSubtitleUntil, 0
|
|
je cs_step_wait
|
|
mov eax, cutsceneTick
|
|
cmp eax, cutsceneSubtitleUntil
|
|
jb cs_step_wait
|
|
mov cutsceneSubtitle, -1
|
|
mov cutsceneSubtitleUntil, 0
|
|
cs_step_wait:
|
|
cmp cutsceneWait, 0
|
|
je cs_dispatch
|
|
dec cutsceneWait
|
|
jnz cs_advance_tick
|
|
cs_dispatch:
|
|
mov eax, cutsceneIndex
|
|
cmp eax, cutsceneCount
|
|
jae cs_advance_tick
|
|
shl eax, 4
|
|
add eax, OFFSET cutsceneEvents
|
|
mov esi, eax
|
|
movzx eax, [esi].CutsceneEvent.tickTimestamp
|
|
cmp eax, cutsceneTick
|
|
ja cs_advance_tick
|
|
inc cutsceneIndex
|
|
movzx eax, [esi].CutsceneEvent.opCode
|
|
cmp eax, OP_END
|
|
je cs_end
|
|
cmp eax, OP_WAIT
|
|
je cs_wait
|
|
cmp eax, OP_MOVE_CAM_TO
|
|
je cs_move
|
|
cmp eax, OP_PAN_CAM_TO
|
|
je cs_pan
|
|
cmp eax, OP_SHOW_SUBTITLE
|
|
je cs_text
|
|
cmp eax, OP_HIDE_SUBTITLE
|
|
je cs_hide_text
|
|
cmp eax, OP_PLAY_SOUND
|
|
je cs_play_sound
|
|
cmp eax, OP_FADE_SCREEN
|
|
je cs_fade
|
|
cmp eax, OP_CHANGE_SUBLEVEL
|
|
je cs_change_level
|
|
jmp cs_end
|
|
cs_wait:
|
|
mov eax, [esi].CutsceneEvent.paramA
|
|
mov cutsceneWait, eax
|
|
test eax, eax
|
|
jnz cs_advance_tick
|
|
jmp cs_dispatch
|
|
cs_move:
|
|
mov eax, csCameraX
|
|
mov csMoveFromX, eax
|
|
mov eax, csCameraY
|
|
mov csMoveFromY, eax
|
|
mov eax, [esi].CutsceneEvent.paramA
|
|
mov csMoveToX, eax
|
|
mov eax, [esi].CutsceneEvent.paramB
|
|
mov csMoveToY, eax
|
|
mov eax, [esi].CutsceneEvent.dataId
|
|
mov csMoveDuration, eax
|
|
mov csMoveElapsed, 0
|
|
test eax, eax
|
|
jnz cs_dispatch
|
|
mov eax, csMoveToX
|
|
mov csCameraX, eax
|
|
mov eax, csMoveToY
|
|
mov csCameraY, eax
|
|
jmp cs_dispatch
|
|
cs_pan:
|
|
mov eax, csCameraAngle
|
|
mov csAngleFrom, eax
|
|
mov ebx, [esi].CutsceneEvent.paramA
|
|
sub ebx, eax
|
|
add ebx, 256
|
|
and ebx, ANGLE_MASK
|
|
sub ebx, 256
|
|
mov csAngleDelta, ebx
|
|
mov csAngleElapsed, 0
|
|
mov eax, [esi].CutsceneEvent.paramB
|
|
mov csAngleDuration, eax
|
|
test eax, eax
|
|
jnz cs_dispatch
|
|
mov eax, [esi].CutsceneEvent.paramA
|
|
mov csCameraAngle, eax
|
|
jmp cs_dispatch
|
|
cs_text:
|
|
mov eax, [esi].CutsceneEvent.paramA
|
|
mov cutsceneSubtitle, eax
|
|
mov eax, [esi].CutsceneEvent.paramB
|
|
test eax, eax
|
|
jz cs_text_expiry_ready
|
|
add eax, cutsceneTick
|
|
cs_text_expiry_ready:
|
|
mov cutsceneSubtitleUntil, eax
|
|
jmp cs_dispatch
|
|
cs_hide_text:
|
|
mov cutsceneSubtitle, -1
|
|
mov cutsceneSubtitleUntil, 0
|
|
jmp cs_dispatch
|
|
cs_play_sound:
|
|
mov eax, [esi].CutsceneEvent.paramA
|
|
cmp eax, -1
|
|
jne cs_sound_start
|
|
invoke PlaySoundA, 0, 0, 0
|
|
jmp cs_dispatch
|
|
cs_sound_start:
|
|
imul eax, CUTSCENE_STRING_BYTES
|
|
add eax, OFFSET cutsceneStrings
|
|
push esi
|
|
mov esi, OFFSET csSoundPrefix
|
|
mov edi, OFFSET csSoundPath
|
|
cs_copy_sound_prefix:
|
|
lodsb
|
|
stosb
|
|
test al, al
|
|
jnz cs_copy_sound_prefix
|
|
dec edi
|
|
pop esi
|
|
mov eax, [esi].CutsceneEvent.paramA
|
|
imul eax, CUTSCENE_STRING_BYTES
|
|
add eax, OFFSET cutsceneStrings
|
|
mov ebx, esi
|
|
mov esi, eax
|
|
cs_copy_sound_name:
|
|
lodsb
|
|
stosb
|
|
test al, al
|
|
jnz cs_copy_sound_name
|
|
movzx eax, [ebx].CutsceneEvent.param8
|
|
shl eax, 3
|
|
or eax, 00020003h ; SND_FILENAME | SND_ASYNC | SND_NODEFAULT (+ SND_LOOP)
|
|
invoke PlaySoundA, OFFSET csSoundPath, 0, eax
|
|
jmp cs_dispatch
|
|
cs_fade:
|
|
mov eax, cutsceneFade
|
|
mov csFadeFrom, eax
|
|
mov eax, [esi].CutsceneEvent.paramA
|
|
mov csFadeTo, eax
|
|
mov eax, [esi].CutsceneEvent.paramB
|
|
mov csFadeDuration, eax
|
|
mov csFadeElapsed, 0
|
|
test eax, eax
|
|
jnz cs_dispatch
|
|
mov eax, csFadeTo
|
|
mov cutsceneFade, eax
|
|
jmp cs_dispatch
|
|
cs_change_level:
|
|
mov eax, cutsceneFade
|
|
mov transitionFade, eax
|
|
mov ebx, [esi].CutsceneEvent.paramA
|
|
imul ebx, sizeof LevelTransition
|
|
add ebx, OFFSET transitionList
|
|
call StopCutscene
|
|
movzx eax, [ebx].LevelTransition.targetSpawnX
|
|
movzx ecx, [ebx].LevelTransition.targetSpawnY
|
|
movzx edx, [ebx].LevelTransition.targetAngle
|
|
movzx esi, [ebx].LevelTransition.fadeEffect
|
|
lea edi, [ebx].LevelTransition.targetMap
|
|
invoke SwitchSublevel, edi, eax, ecx, edx, esi
|
|
cmp fadeState, 1
|
|
jne cs_transition_ready
|
|
mov eax, transitionFade
|
|
cmp eax, fadeLevel
|
|
jbe cs_transition_ready
|
|
mov fadeLevel, eax ; continue from the cinematic fade without flashing bright
|
|
cs_transition_ready:
|
|
ret
|
|
cs_end:
|
|
call StopCutscene
|
|
ret
|
|
cs_advance_tick:
|
|
inc cutsceneTick
|
|
cs_update_done:
|
|
ret
|
|
UpdateCutscene ENDP
|
|
|
|
; Letterbox (320 / 2.35 -> 136-pixel picture) and up to three wrapped lines.
|
|
RenderCutsceneOverlay PROC uses ebx ecx edx esi edi
|
|
LOCAL row:DWORD, count:DWORD, pixelY:DWORD
|
|
invoke DrawRect, 0, 0, SCREEN_W, 52, COLOR_BLACK
|
|
invoke DrawRect, 0, 188, SCREEN_W, 52, COLOR_BLACK
|
|
invoke DrawString8x8, 232, 10, OFFSET strSkipScene, COLOR_GRAY
|
|
mov eax, cutsceneSubtitle
|
|
cmp eax, -1
|
|
je cs_overlay_done
|
|
cmp eax, cutsceneStringCount
|
|
jae cs_overlay_done
|
|
imul eax, CUTSCENE_STRING_BYTES
|
|
add eax, OFFSET cutsceneStrings
|
|
mov esi, eax
|
|
; Semitransparent subtitle panel over the bottom of the picture.
|
|
mov edi, OFFSET screenBuffer + 158*SCREEN_PITCH
|
|
mov ecx, 30*SCREEN_W
|
|
cs_subtitle_dim:
|
|
mov eax, [edi]
|
|
and eax, 00FEFEFEh
|
|
shr eax, 1
|
|
stosd
|
|
loop cs_subtitle_dim
|
|
mov row, 0
|
|
cs_subtitle_line:
|
|
mov edi, OFFSET csLineBuffer
|
|
mov count, 0
|
|
cs_subtitle_char:
|
|
lodsb
|
|
test al, al
|
|
jz cs_subtitle_string_end
|
|
cmp al, 10
|
|
je cs_subtitle_emit
|
|
stosb
|
|
inc count
|
|
cmp count, 38
|
|
jb cs_subtitle_char
|
|
jmp cs_subtitle_emit
|
|
cs_subtitle_string_end:
|
|
dec esi
|
|
cs_subtitle_emit:
|
|
mov byte ptr [edi], 0
|
|
mov eax, count
|
|
shl eax, 2
|
|
mov ebx, SCREEN_HALF_W
|
|
sub ebx, eax
|
|
mov eax, row
|
|
imul eax, 10
|
|
add eax, 160
|
|
invoke DrawString8x8, ebx, eax, OFFSET csLineBuffer, COLOR_WHITE
|
|
cmp byte ptr [esi], 0
|
|
je cs_overlay_done
|
|
inc row
|
|
cmp row, 3
|
|
jb cs_subtitle_line
|
|
cs_overlay_done:
|
|
ret
|
|
RenderCutsceneOverlay ENDP
|
|
|
|
ApplyCutsceneFade PROC uses ebx ecx edx esi edi
|
|
mov ebx, 16
|
|
sub ebx, cutsceneFade
|
|
cmp ebx, 16
|
|
jae cs_fade_done
|
|
mov edi, OFFSET screenBuffer
|
|
mov ecx, SCREEN_W*SCREEN_H
|
|
cs_fade_pixel:
|
|
mov eax, [edi]
|
|
movzx edx, al
|
|
imul edx, ebx
|
|
shr edx, 4
|
|
movzx eax, ah
|
|
imul eax, ebx
|
|
shr eax, 4
|
|
shl eax, 8
|
|
or edx, eax
|
|
mov eax, [edi]
|
|
shr eax, 16
|
|
imul eax, ebx
|
|
shr eax, 4
|
|
shl eax, 16
|
|
or eax, edx
|
|
stosd
|
|
loop cs_fade_pixel
|
|
cs_fade_done:
|
|
ret
|
|
ApplyCutsceneFade ENDP
|