; 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 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 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 playerX, eax mov eax, csMoveToY sub eax, csMoveFromY imul csMoveElapsed idiv csMoveDuration add eax, csMoveFromY mov playerY, 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 playerAngle, 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, playerX mov csMoveFromX, eax mov eax, playerY 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 playerX, eax mov eax, csMoveToY mov playerY, eax jmp cs_dispatch cs_pan: mov eax, playerAngle 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 playerAngle, 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