From 0f9457e0d1202c2bba50ab009bab14fc003accda Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Thu, 8 Jan 2026 21:00:05 +0100 Subject: [PATCH] video_core: Fix crash caused by malformed geo shaders (#1585) --- src/video_core/pica/pica_core.cpp | 6 ++++-- src/video_core/shader/shader_interpreter.cpp | 18 ++++++++++++++++-- .../shader/shader_jit_a64_compiler.cpp | 18 ++++++++++++++++-- .../shader/shader_jit_x64_compiler.cpp | 18 ++++++++++++++++-- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/video_core/pica/pica_core.cpp b/src/video_core/pica/pica_core.cpp index d12a986c1..359fe0ff1 100644 --- a/src/video_core/pica/pica_core.cpp +++ b/src/video_core/pica/pica_core.cpp @@ -342,7 +342,8 @@ void PicaCore::WriteInternalReg(u32 id, u32 value, u32 mask, bool& stop_requeste } else { vs_setup.program_code[offset] = value; vs_setup.MarkProgramCodeDirty(); - if (!regs.internal.pipeline.gs_unit_exclusive_configuration) { + if (!regs.internal.pipeline.gs_unit_exclusive_configuration && + regs.internal.pipeline.use_gs == PipelineRegs::UseGS::No) { gs_setup.program_code[offset] = value; gs_setup.MarkProgramCodeDirty(); } @@ -365,7 +366,8 @@ void PicaCore::WriteInternalReg(u32 id, u32 value, u32 mask, bool& stop_requeste } else { vs_setup.swizzle_data[offset] = value; vs_setup.MarkSwizzleDataDirty(); - if (!regs.internal.pipeline.gs_unit_exclusive_configuration) { + if (!regs.internal.pipeline.gs_unit_exclusive_configuration && + regs.internal.pipeline.use_gs == PipelineRegs::UseGS::No) { gs_setup.swizzle_data[offset] = value; gs_setup.MarkSwizzleDataDirty(); } diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp index bf19c037f..b8da94941 100644 --- a/src/video_core/shader/shader_interpreter.cpp +++ b/src/video_core/shader/shader_interpreter.cpp @@ -1,4 +1,4 @@ -// Copyright 2014 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -118,7 +118,21 @@ static void RunInterpreter(const ShaderSetup& setup, ShaderUnit& state, bool is_break = false; const u32 old_program_counter = program_counter; - const Instruction instr = {program_code[program_counter]}; + // Always treat the last instruction of the program code as an + // end instruction. This fixes some games such as Thunder Blade + // or After Burner II which have malformed geo shaders without an + // end instruction crashing the emulator due to the program counter + // growing uncontrollably. + // TODO(PabloMK7): Find how real HW reacts to this, most likely the + // program counter wraps around after reaching the last instruction, + // but more testing is needed. + Instruction instr{}; + if (program_counter < MAX_PROGRAM_CODE_LENGTH - 1) { + instr.hex = program_code[program_counter]; + } else { + instr.opcode.Assign(OpCode::Id::END); + } + const SwizzlePattern swizzle = {swizzle_data[instr.common.operand_desc_id]}; Record(debug_data, iteration, program_counter); diff --git a/src/video_core/shader/shader_jit_a64_compiler.cpp b/src/video_core/shader/shader_jit_a64_compiler.cpp index 18793317e..3c9786b29 100644 --- a/src/video_core/shader/shader_jit_a64_compiler.cpp +++ b/src/video_core/shader/shader_jit_a64_compiler.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -899,7 +899,21 @@ void JitShader::Compile_NextInstr() { l(instruction_labels[program_counter]); - const Instruction instr = {(*program_code)[program_counter++]}; + // Always treat the last instruction of the program code as an + // end instruction. This fixes some games such as Thunder Blade + // or After Burner II which have malformed geo shaders without an + // end instruction crashing the emulator due to the program counter + // growing uncontrollably. + // TODO(PabloMK7): Find how real HW reacts to this, most likely the + // program counter wraps around after reaching the last instruction, + // but more testing is needed. + Instruction instr{}; + if (program_counter < MAX_PROGRAM_CODE_LENGTH - 1) { + instr.hex = (*program_code)[program_counter]; + } else { + instr.opcode.Assign(OpCode::Id::END); + } + ++program_counter; const OpCode::Id opcode = instr.opcode.Value(); const auto instr_func = instr_table[static_cast(opcode)]; diff --git a/src/video_core/shader/shader_jit_x64_compiler.cpp b/src/video_core/shader/shader_jit_x64_compiler.cpp index fd47ac0a1..84cdc09bc 100644 --- a/src/video_core/shader/shader_jit_x64_compiler.cpp +++ b/src/video_core/shader/shader_jit_x64_compiler.cpp @@ -1,4 +1,4 @@ -// Copyright 2015 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -936,7 +936,21 @@ void JitShader::Compile_NextInstr() { L(instruction_labels[program_counter]); - Instruction instr = {(*program_code)[program_counter++]}; + // Always treat the last instruction of the program code as an + // end instruction. This fixes some games such as Thunder Blade + // or After Burner II which have malformed geo shaders without an + // end instruction crashing the emulator due to the program counter + // growing uncontrollably. + // TODO(PabloMK7): Find how real HW reacts to this, most likely the + // program counter wraps around after reaching the last instruction, + // but more testing is needed. + Instruction instr{}; + if (program_counter < MAX_PROGRAM_CODE_LENGTH - 1) { + instr.hex = (*program_code)[program_counter]; + } else { + instr.opcode.Assign(OpCode::Id::END); + } + ++program_counter; OpCode::Id opcode = instr.opcode.Value(); auto instr_func = instr_table[static_cast(opcode)];