From 65c1a7b2052ed65ef645420c17e0e6ad0dbbc835 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 16 Mar 2026 20:43:54 +0200 Subject: [PATCH 01/11] handle src1 as VectorGPR in cmp_u64 --- .../frontend/translate/vector_alu.cpp | 116 +++++++++++++----- 1 file changed, 85 insertions(+), 31 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 08a0f6527..720dd2e27 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -1138,12 +1138,6 @@ void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) { const bool is_zero = inst.src[1].field == OperandField::ConstZero; const bool is_neg_one = inst.src[1].field == OperandField::SignedConstIntNeg; - ASSERT(is_zero || is_neg_one); - if (is_neg_one) { - ASSERT_MSG(-s32(inst.src[1].code) + SignedConstIntNegMin - 1 == -1, - "SignedConstIntNeg must be -1"); - } - const IR::U1 src0 = [&] { switch (inst.src[0].field) { case OperandField::ScalarGPR: @@ -1154,37 +1148,97 @@ void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const UNREACHABLE_MSG("src0 = {}", u32(inst.src[0].field)); } }(); - const IR::U1 result = [&] { - switch (op) { - case ConditionOp::EQ: - return is_zero ? ir.LogicalNot(src0) : src0; - case ConditionOp::LG: // NE - return is_zero ? src0 : ir.LogicalNot(src0); - case ConditionOp::GT: - ASSERT(is_zero); - return ir.GroupAny(ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code))); - default: - UNREACHABLE_MSG("Unsupported V_CMP_U64 condition operation: {}", u32(op)); + + // For constant cases + if (is_zero || is_neg_one) { + if (is_neg_one) { + ASSERT_MSG(-s32(inst.src[1].code) + SignedConstIntNegMin - 1 == -1, + "SignedConstIntNeg must be -1"); } - }(); - if (is_signed) { - UNREACHABLE_MSG("V_CMP_U64 with signed integers is not supported"); - } - if (set_exec) { - UNREACHABLE_MSG("Exec setting for V_CMP_U64 is not supported"); + const IR::U1 result = [&] { + switch (op) { + case ConditionOp::EQ: + return is_zero ? ir.LogicalNot(src0) : src0; + case ConditionOp::LG: // NE + return is_zero ? src0 : ir.LogicalNot(src0); + case ConditionOp::GT: + ASSERT(is_zero); + return ir.GroupAny(ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code))); + default: + UNREACHABLE_MSG("Unsupported V_CMP_U64 condition operation: {}", u32(op)); + } + }(); + + if (is_signed) { + UNREACHABLE_MSG("V_CMP_U64 with signed integers is not supported"); + } + if (set_exec) { + UNREACHABLE_MSG("Exec setting for V_CMP_U64 is not supported"); + } + + switch (inst.dst[1].field) { + case OperandField::VccLo: + return ir.SetVcc(result); + case OperandField::ScalarGPR: + return ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), result); + default: + UNREACHABLE(); + } + return; } - switch (inst.dst[1].field) { - case OperandField::VccLo: - return ir.SetVcc(result); - case OperandField::ScalarGPR: - return ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), result); - default: - UNREACHABLE(); + // Handle VectorGPR case - src1 is a 64-bit vector value + if (inst.src[1].field == OperandField::VectorGPR) { + // Get the 64-bit vector value from two consecutive VGPRs + const IR::U32 low = ir.GetVectorReg(IR::VectorReg(inst.src[1].code)); + const IR::U32 high = ir.GetVectorReg(IR::VectorReg(inst.src[1].code + 1)); + + const IR::U64 src1_64 = + ir.PackUint2x32(ir.CompositeConstruct(low, high)); // Combine into 64-bit value + + const IR::Value src0_val = ir.Select(src0, ir.Imm32(1), ir.Imm32(0)); + const IR::U32 src0_32 = IR::U32{src0_val}; + const IR::U64 src0_64 = ir.PackUint2x32(ir.CompositeConstruct(src0_32, ir.Imm32(0U))); + + // Perform the 64-bit comparison + const IR::U1 result = [&] { + switch (op) { + case ConditionOp::EQ: + return ir.IEqual(src0_64, src1_64); + case ConditionOp::LG: // NE + return ir.INotEqual(src0_64, src1_64); + case ConditionOp::GT: + return ir.IGreaterThan(src0_64, src1_64, false); + default: + UNREACHABLE_MSG("Unsupported V_CMP_U64 condition operation: {}", u32(op)); + } + }(); + + if (is_signed) { + UNREACHABLE_MSG("V_CMP_U64 with signed integers is not supported"); + } + if (set_exec) { + ir.SetExec(result); + } + + switch (inst.dst[1].field) { + case OperandField::VccLo: + return ir.SetVcc(result); + case OperandField::ScalarGPR: + return ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), result); + case OperandField::VectorGPR: { + IR::Value result_val = ir.Select(result, ir.Imm32(1), ir.Imm32(0)); + ir.SetVectorReg(IR::VectorReg(inst.dst[1].code), IR::U32{result_val}); + } break; + default: + UNREACHABLE_MSG("Unsupported dst field: {}", u32(inst.dst[1].field)); + } + return; } + + UNREACHABLE_MSG("Unsupported src[1] operand field for V_CMP_U64: {}", u32(inst.src[1].field)); } - void Translator::V_CMP_CLASS_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; From ab8f4fdfc73d5c52f24bd805ed2e5344c3d9f3f8 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 16 Mar 2026 20:45:43 +0200 Subject: [PATCH 02/11] exec is not permitted --- src/shader_recompiler/frontend/translate/vector_alu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 720dd2e27..76456df13 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -1219,7 +1219,7 @@ void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const UNREACHABLE_MSG("V_CMP_U64 with signed integers is not supported"); } if (set_exec) { - ir.SetExec(result); + UNREACHABLE_MSG("Exec setting for V_CMP_U64 is not supported"); } switch (inst.dst[1].field) { From 760133c9ccff0c80ac38743ea38076ac6fc0704f Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 16 Mar 2026 21:23:51 +0200 Subject: [PATCH 03/11] made cmp_u64 a real u64 compare use it at your own risk --- .../frontend/translate/vector_alu.cpp | 181 +++++++++--------- 1 file changed, 90 insertions(+), 91 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 76456df13..d57fe57bf 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -1136,109 +1136,108 @@ void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const } void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) { - const bool is_zero = inst.src[1].field == OperandField::ConstZero; - const bool is_neg_one = inst.src[1].field == OperandField::SignedConstIntNeg; - const IR::U1 src0 = [&] { + const IR::U64 src0 = [&]() -> IR::U64 { switch (inst.src[0].field) { - case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); - case OperandField::VccLo: - return ir.GetVcc(); + case OperandField::ScalarGPR: { + IR::U32 low = ir.GetScalarReg(IR::ScalarReg(inst.src[0].code)); + IR::U32 high = ir.GetScalarReg(IR::ScalarReg(inst.src[0].code + 1)); + return ir.PackUint2x32(ir.CompositeConstruct(low, high)); + } + case OperandField::VectorGPR: { + IR::U32 low = ir.GetVectorReg(IR::VectorReg(inst.src[0].code)); + IR::U32 high = ir.GetVectorReg(IR::VectorReg(inst.src[0].code + 1)); + return ir.PackUint2x32(ir.CompositeConstruct(low, high)); + } + case OperandField::VccLo: { + IR::U1 vcc_bit = ir.GetVcc(); + // Fix: Cast both Imm64 calls to avoid ambiguity + IR::Value vcc_val = + ir.Select(vcc_bit, ir.Imm64(static_cast(-1)), ir.Imm64(static_cast(0))); + return IR::U64{vcc_val}; + } + case OperandField::ConstZero: + return ir.Imm64(static_cast(0)); + case OperandField::SignedConstIntPos: + return ir.Imm64(static_cast(inst.src[0].code)); + case OperandField::SignedConstIntNeg: + return ir.Imm64(static_cast(-s32(inst.src[0].code))); default: UNREACHABLE_MSG("src0 = {}", u32(inst.src[0].field)); } }(); - // For constant cases - if (is_zero || is_neg_one) { - if (is_neg_one) { - ASSERT_MSG(-s32(inst.src[1].code) + SignedConstIntNegMin - 1 == -1, - "SignedConstIntNeg must be -1"); + const IR::U64 src1 = [&]() -> IR::U64 { + switch (inst.src[1].field) { + case OperandField::ScalarGPR: { + IR::U32 low = ir.GetScalarReg(IR::ScalarReg(inst.src[1].code)); + IR::U32 high = ir.GetScalarReg(IR::ScalarReg(inst.src[1].code + 1)); + return ir.PackUint2x32(ir.CompositeConstruct(low, high)); } - - const IR::U1 result = [&] { - switch (op) { - case ConditionOp::EQ: - return is_zero ? ir.LogicalNot(src0) : src0; - case ConditionOp::LG: // NE - return is_zero ? src0 : ir.LogicalNot(src0); - case ConditionOp::GT: - ASSERT(is_zero); - return ir.GroupAny(ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code))); - default: - UNREACHABLE_MSG("Unsupported V_CMP_U64 condition operation: {}", u32(op)); - } - }(); - - if (is_signed) { - UNREACHABLE_MSG("V_CMP_U64 with signed integers is not supported"); - } - if (set_exec) { - UNREACHABLE_MSG("Exec setting for V_CMP_U64 is not supported"); - } - - switch (inst.dst[1].field) { - case OperandField::VccLo: - return ir.SetVcc(result); - case OperandField::ScalarGPR: - return ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), result); - default: - UNREACHABLE(); - } - return; - } - - // Handle VectorGPR case - src1 is a 64-bit vector value - if (inst.src[1].field == OperandField::VectorGPR) { - // Get the 64-bit vector value from two consecutive VGPRs - const IR::U32 low = ir.GetVectorReg(IR::VectorReg(inst.src[1].code)); - const IR::U32 high = ir.GetVectorReg(IR::VectorReg(inst.src[1].code + 1)); - - const IR::U64 src1_64 = - ir.PackUint2x32(ir.CompositeConstruct(low, high)); // Combine into 64-bit value - - const IR::Value src0_val = ir.Select(src0, ir.Imm32(1), ir.Imm32(0)); - const IR::U32 src0_32 = IR::U32{src0_val}; - const IR::U64 src0_64 = ir.PackUint2x32(ir.CompositeConstruct(src0_32, ir.Imm32(0U))); - - // Perform the 64-bit comparison - const IR::U1 result = [&] { - switch (op) { - case ConditionOp::EQ: - return ir.IEqual(src0_64, src1_64); - case ConditionOp::LG: // NE - return ir.INotEqual(src0_64, src1_64); - case ConditionOp::GT: - return ir.IGreaterThan(src0_64, src1_64, false); - default: - UNREACHABLE_MSG("Unsupported V_CMP_U64 condition operation: {}", u32(op)); - } - }(); - - if (is_signed) { - UNREACHABLE_MSG("V_CMP_U64 with signed integers is not supported"); - } - if (set_exec) { - UNREACHABLE_MSG("Exec setting for V_CMP_U64 is not supported"); - } - - switch (inst.dst[1].field) { - case OperandField::VccLo: - return ir.SetVcc(result); - case OperandField::ScalarGPR: - return ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), result); case OperandField::VectorGPR: { - IR::Value result_val = ir.Select(result, ir.Imm32(1), ir.Imm32(0)); - ir.SetVectorReg(IR::VectorReg(inst.dst[1].code), IR::U32{result_val}); - } break; - default: - UNREACHABLE_MSG("Unsupported dst field: {}", u32(inst.dst[1].field)); + IR::U32 low = ir.GetVectorReg(IR::VectorReg(inst.src[1].code)); + IR::U32 high = ir.GetVectorReg(IR::VectorReg(inst.src[1].code + 1)); + return ir.PackUint2x32(ir.CompositeConstruct(low, high)); } - return; + case OperandField::VccLo: { + IR::U1 vcc_bit = ir.GetVcc(); + IR::Value vcc_val = + ir.Select(vcc_bit, ir.Imm64(static_cast(-1)), ir.Imm64(static_cast(0))); + return IR::U64{vcc_val}; + } + case OperandField::ConstZero: + return ir.Imm64(static_cast(0)); + case OperandField::SignedConstIntPos: + return ir.Imm64(static_cast(inst.src[1].code)); + case OperandField::SignedConstIntNeg: + return ir.Imm64(static_cast(-s32(inst.src[1].code))); + default: + UNREACHABLE_MSG("Unsupported src[1] operand field: {}", u32(inst.src[1].field)); + } + }(); + + // Perform the 64-bit comparison + const IR::U1 result = [&] { + switch (op) { + case ConditionOp::EQ: + return ir.IEqual(src0, src1); + case ConditionOp::LG: // NE + return ir.INotEqual(src0, src1); + case ConditionOp::GT: + return ir.IGreaterThan(src0, src1, false); // false = unsigned + case ConditionOp::LT: + return ir.ILessThan(src0, src1, false); + case ConditionOp::GE: + return ir.IGreaterThanEqual(src0, src1, false); + case ConditionOp::LE: + return ir.ILessThanEqual(src0, src1, false); + default: + UNREACHABLE_MSG("Unsupported V_CMP_U64 condition operation: {}", u32(op)); + } + }(); + + // Handle flags + if (is_signed) { + UNREACHABLE_MSG("V_CMP_U64 with signed integers is not supported"); + } + if (set_exec) { + UNREACHABLE_MSG("Exec setting for V_CMP_U64 is not supported"); } - UNREACHABLE_MSG("Unsupported src[1] operand field for V_CMP_U64: {}", u32(inst.src[1].field)); + // Write result (1-bit boolean) to destination + switch (inst.dst[1].field) { + case OperandField::VccLo: + return ir.SetVcc(result); + case OperandField::ScalarGPR: + return ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), result); + case OperandField::VectorGPR: { + IR::Value result_val = ir.Select(result, ir.Imm32(1), ir.Imm32(0)); + ir.SetVectorReg(IR::VectorReg(inst.dst[1].code), IR::U32{result_val}); + } break; + default: + UNREACHABLE_MSG("Unsupported dst field: {}", u32(inst.dst[1].field)); + } } + void Translator::V_CMP_CLASS_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; From 221629b8b59ca4d9f7ed08fb0eb1621d1130d6a9 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:13:24 -0500 Subject: [PATCH 04/11] Select for U64 constants --- src/shader_recompiler/backend/spirv/emit_spirv_instructions.h | 1 + src/shader_recompiler/backend/spirv/emit_spirv_select.cpp | 4 ++++ src/shader_recompiler/ir/ir_emitter.cpp | 2 ++ src/shader_recompiler/ir/opcodes.inc | 1 + 4 files changed, 8 insertions(+) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 69fa36eaa..110cbf5fa 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -191,6 +191,7 @@ Id EmitCompositeShuffleF32x4(EmitContext& ctx, Id composite1, Id composite2, u32 u32 comp2, u32 comp3); Id EmitSelectU1(EmitContext& ctx, Id cond, Id true_value, Id false_value); Id EmitSelectU32(EmitContext& ctx, Id cond, Id true_value, Id false_value); +Id EmitSelectU64(EmitContext& ctx, Id cond, Id true_value, Id false_value); Id EmitSelectF32(EmitContext& ctx, Id cond, Id true_value, Id false_value); Id EmitBitCastU16F16(EmitContext& ctx, Id value); Id EmitBitCastU32F32(EmitContext& ctx, Id value); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_select.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_select.cpp index 1bfe4ea3c..752074aa4 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_select.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_select.cpp @@ -14,6 +14,10 @@ Id EmitSelectU32(EmitContext& ctx, Id cond, Id true_value, Id false_value) { return ctx.OpSelect(ctx.U32[1], cond, true_value, false_value); } +Id EmitSelectU64(EmitContext& ctx, Id cond, Id true_value, Id false_value) { + return ctx.OpSelect(ctx.U64, cond, true_value, false_value); +} + Id EmitSelectF32(EmitContext& ctx, Id cond, Id true_value, Id false_value) { return ctx.OpSelect(ctx.F32[1], cond, true_value, false_value); } diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index c681c3120..835a6186d 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -886,6 +886,8 @@ Value IREmitter::Select(const U1& condition, const Value& true_value, const Valu return Inst(Opcode::SelectU1, condition, true_value, false_value); case Type::U32: return Inst(Opcode::SelectU32, condition, true_value, false_value); + case Type::U64: + return Inst(Opcode::SelectU64, condition, true_value, false_value); case Type::F32: return Inst(Opcode::SelectF32, condition, true_value, false_value); default: diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 6304a96fa..ab395e8f9 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -182,6 +182,7 @@ OPCODE(CompositeShuffleF32x4, F32x4, F32x // Select operations OPCODE(SelectU1, U1, U1, U1, U1, ) OPCODE(SelectU32, U32, U1, U32, U32, ) +OPCODE(SelectU64, U64, U1, U32, U32, ) OPCODE(SelectF32, F32, U1, F32, F32, ) // Bitwise conversions From ff40a9240f414843fcb8d8dd171d2c6b56c43624 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 16 Mar 2026 23:27:24 +0200 Subject: [PATCH 05/11] SignedConstIntNeg values are encoded relative to a base value --- src/shader_recompiler/frontend/translate/vector_alu.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index d57fe57bf..0d4156ee1 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -1160,7 +1160,8 @@ void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const case OperandField::SignedConstIntPos: return ir.Imm64(static_cast(inst.src[0].code)); case OperandField::SignedConstIntNeg: - return ir.Imm64(static_cast(-s32(inst.src[0].code))); + s32 decoded_value = -s32(inst.src[0].code) + SignedConstIntNegMin - 1; + return ir.Imm64(static_cast(decoded_value)); default: UNREACHABLE_MSG("src0 = {}", u32(inst.src[0].field)); } @@ -1189,7 +1190,8 @@ void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const case OperandField::SignedConstIntPos: return ir.Imm64(static_cast(inst.src[1].code)); case OperandField::SignedConstIntNeg: - return ir.Imm64(static_cast(-s32(inst.src[1].code))); + s32 decoded_value = -s32(inst.src[0].code) + SignedConstIntNegMin - 1; + return ir.Imm64(static_cast(decoded_value)); default: UNREACHABLE_MSG("Unsupported src[1] operand field: {}", u32(inst.src[1].field)); } From 5535b3f5aba4b988518fc595fa1170c497ed76e8 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 16 Mar 2026 23:35:48 +0200 Subject: [PATCH 06/11] brackets --- src/shader_recompiler/frontend/translate/vector_alu.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 0d4156ee1..e819e09bc 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -1159,9 +1159,10 @@ void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const return ir.Imm64(static_cast(0)); case OperandField::SignedConstIntPos: return ir.Imm64(static_cast(inst.src[0].code)); - case OperandField::SignedConstIntNeg: + case OperandField::SignedConstIntNeg: { s32 decoded_value = -s32(inst.src[0].code) + SignedConstIntNegMin - 1; return ir.Imm64(static_cast(decoded_value)); + } default: UNREACHABLE_MSG("src0 = {}", u32(inst.src[0].field)); } @@ -1189,9 +1190,10 @@ void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const return ir.Imm64(static_cast(0)); case OperandField::SignedConstIntPos: return ir.Imm64(static_cast(inst.src[1].code)); - case OperandField::SignedConstIntNeg: + case OperandField::SignedConstIntNeg: { s32 decoded_value = -s32(inst.src[0].code) + SignedConstIntNegMin - 1; return ir.Imm64(static_cast(decoded_value)); + } default: UNREACHABLE_MSG("Unsupported src[1] operand field: {}", u32(inst.src[1].field)); } From 6ea85878262bcb47229ec651fc6ee3a6e61de167 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 16 Mar 2026 23:51:32 +0200 Subject: [PATCH 07/11] fixed typo --- src/shader_recompiler/frontend/translate/vector_alu.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index e819e09bc..8d8f151c2 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -1150,7 +1150,6 @@ void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const } case OperandField::VccLo: { IR::U1 vcc_bit = ir.GetVcc(); - // Fix: Cast both Imm64 calls to avoid ambiguity IR::Value vcc_val = ir.Select(vcc_bit, ir.Imm64(static_cast(-1)), ir.Imm64(static_cast(0))); return IR::U64{vcc_val}; @@ -1191,7 +1190,7 @@ void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const case OperandField::SignedConstIntPos: return ir.Imm64(static_cast(inst.src[1].code)); case OperandField::SignedConstIntNeg: { - s32 decoded_value = -s32(inst.src[0].code) + SignedConstIntNegMin - 1; + s32 decoded_value = -s32(inst.src[1].code) + SignedConstIntNegMin - 1; return ir.Imm64(static_cast(decoded_value)); } default: From 17b38edeb505e536f2332229207d20b1565edff1 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 17 Mar 2026 07:38:32 +0200 Subject: [PATCH 08/11] avoid using u64 emmiter --- .../frontend/translate/vector_alu.cpp | 181 +++++++++--------- 1 file changed, 95 insertions(+), 86 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 8d8f151c2..07a3e88da 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -1136,108 +1136,117 @@ void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const } void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) { - const IR::U64 src0 = [&]() -> IR::U64 { - switch (inst.src[0].field) { - case OperandField::ScalarGPR: { - IR::U32 low = ir.GetScalarReg(IR::ScalarReg(inst.src[0].code)); - IR::U32 high = ir.GetScalarReg(IR::ScalarReg(inst.src[0].code + 1)); - return ir.PackUint2x32(ir.CompositeConstruct(low, high)); - } - case OperandField::VectorGPR: { - IR::U32 low = ir.GetVectorReg(IR::VectorReg(inst.src[0].code)); - IR::U32 high = ir.GetVectorReg(IR::VectorReg(inst.src[0].code + 1)); - return ir.PackUint2x32(ir.CompositeConstruct(low, high)); - } - case OperandField::VccLo: { - IR::U1 vcc_bit = ir.GetVcc(); - IR::Value vcc_val = - ir.Select(vcc_bit, ir.Imm64(static_cast(-1)), ir.Imm64(static_cast(0))); - return IR::U64{vcc_val}; - } - case OperandField::ConstZero: - return ir.Imm64(static_cast(0)); - case OperandField::SignedConstIntPos: - return ir.Imm64(static_cast(inst.src[0].code)); - case OperandField::SignedConstIntNeg: { - s32 decoded_value = -s32(inst.src[0].code) + SignedConstIntNegMin - 1; - return ir.Imm64(static_cast(decoded_value)); - } - default: - UNREACHABLE_MSG("src0 = {}", u32(inst.src[0].field)); - } - }(); + ASSERT(!is_signed); - const IR::U64 src1 = [&]() -> IR::U64 { - switch (inst.src[1].field) { - case OperandField::ScalarGPR: { - IR::U32 low = ir.GetScalarReg(IR::ScalarReg(inst.src[1].code)); - IR::U32 high = ir.GetScalarReg(IR::ScalarReg(inst.src[1].code + 1)); - return ir.PackUint2x32(ir.CompositeConstruct(low, high)); - } - case OperandField::VectorGPR: { - IR::U32 low = ir.GetVectorReg(IR::VectorReg(inst.src[1].code)); - IR::U32 high = ir.GetVectorReg(IR::VectorReg(inst.src[1].code + 1)); - return ir.PackUint2x32(ir.CompositeConstruct(low, high)); - } - case OperandField::VccLo: { - IR::U1 vcc_bit = ir.GetVcc(); - IR::Value vcc_val = - ir.Select(vcc_bit, ir.Imm64(static_cast(-1)), ir.Imm64(static_cast(0))); - return IR::U64{vcc_val}; - } - case OperandField::ConstZero: - return ir.Imm64(static_cast(0)); - case OperandField::SignedConstIntPos: - return ir.Imm64(static_cast(inst.src[1].code)); - case OperandField::SignedConstIntNeg: { - s32 decoded_value = -s32(inst.src[1].code) + SignedConstIntNegMin - 1; - return ir.Imm64(static_cast(decoded_value)); - } - default: - UNREACHABLE_MSG("Unsupported src[1] operand field: {}", u32(inst.src[1].field)); - } - }(); + // Lambda to read a 64-bit operand as two 32-bit parts + auto read_u64 = [&](const InstOperand& op, bool is_src0) -> std::pair { + using enum OperandField; - // Perform the 64-bit comparison - const IR::U1 result = [&] { - switch (op) { - case ConditionOp::EQ: - return ir.IEqual(src0, src1); - case ConditionOp::LG: // NE - return ir.INotEqual(src0, src1); - case ConditionOp::GT: - return ir.IGreaterThan(src0, src1, false); // false = unsigned - case ConditionOp::LT: - return ir.ILessThan(src0, src1, false); - case ConditionOp::GE: - return ir.IGreaterThanEqual(src0, src1, false); - case ConditionOp::LE: - return ir.ILessThanEqual(src0, src1, false); - default: - UNREACHABLE_MSG("Unsupported V_CMP_U64 condition operation: {}", u32(op)); + switch (op.field) { + case ScalarGPR: + return {ir.GetScalarReg(IR::ScalarReg(op.code)), + ir.GetScalarReg(IR::ScalarReg(op.code + 1))}; + + case VectorGPR: + return {ir.GetVectorReg(IR::VectorReg(op.code)), + ir.GetVectorReg(IR::VectorReg(op.code + 1))}; + + case ConstZero: + return {ir.Imm32(0), ir.Imm32(0)}; + + case SignedConstIntPos: + return {ir.Imm32(op.code), ir.Imm32(0)}; + + case SignedConstIntNeg: { + s32 v = -s32(op.code) + SignedConstIntNegMin - 1; + return {ir.Imm32(v), ir.Imm32(v < 0 ? -1 : 0)}; } - }(); + case VccLo: + // treat as 32-bit values, zero-extend to 64-bit + { + IR::U32 val = ir.GetScalarReg(IR::ScalarReg(op.code)); + return {val, ir.Imm32(0)}; + } + default: + UNREACHABLE_MSG("Unsupported operand field {}", u32(op.field)); + } + }; + + const auto [a_lo, a_hi] = read_u64(inst.src[0], true); + const auto [b_lo, b_hi] = read_u64(inst.src[1], false); + + IR::U1 cmp_result{}; + switch (op) { + case ConditionOp::EQ: { + IR::U1 hi_eq = ir.IEqual(a_hi, b_hi); + IR::U1 lo_eq = ir.IEqual(a_lo, b_lo); + cmp_result = ir.LogicalAnd(hi_eq, lo_eq); + break; + } + + case ConditionOp::LG: { // NE + IR::U1 hi_ne = ir.INotEqual(a_hi, b_hi); + IR::U1 lo_ne = ir.INotEqual(a_lo, b_lo); + cmp_result = ir.LogicalOr(hi_ne, lo_ne); + break; + } + + case ConditionOp::GT: { + IR::U1 hi_gt = ir.IGreaterThan(a_hi, b_hi, false); + IR::U1 hi_eq = ir.IEqual(a_hi, b_hi); + IR::U1 lo_gt = ir.IGreaterThan(a_lo, b_lo, false); + cmp_result = ir.LogicalOr(hi_gt, ir.LogicalAnd(hi_eq, lo_gt)); + break; + } + + case ConditionOp::LT: { + IR::U1 hi_lt = ir.ILessThan(a_hi, b_hi, false); + IR::U1 hi_eq = ir.IEqual(a_hi, b_hi); + IR::U1 lo_lt = ir.ILessThan(a_lo, b_lo, false); + cmp_result = ir.LogicalOr(hi_lt, ir.LogicalAnd(hi_eq, lo_lt)); + break; + } + + case ConditionOp::GE: { + IR::U1 hi_gt = ir.IGreaterThan(a_hi, b_hi, false); + IR::U1 hi_eq = ir.IEqual(a_hi, b_hi); + IR::U1 lo_ge = ir.IGreaterThanEqual(a_lo, b_lo, false); + cmp_result = ir.LogicalOr(hi_gt, ir.LogicalAnd(hi_eq, lo_ge)); + break; + } + + case ConditionOp::LE: { + IR::U1 hi_lt = ir.ILessThan(a_hi, b_hi, false); + IR::U1 hi_eq = ir.IEqual(a_hi, b_hi); + IR::U1 lo_le = ir.ILessThanEqual(a_lo, b_lo, false); + cmp_result = ir.LogicalOr(hi_lt, ir.LogicalAnd(hi_eq, lo_le)); + break; + } + + default: + UNREACHABLE_MSG("Unsupported V_CMP_U64 condition {}", u32(op)); + } // Handle flags if (is_signed) { UNREACHABLE_MSG("V_CMP_U64 with signed integers is not supported"); } + if (set_exec) { UNREACHABLE_MSG("Exec setting for V_CMP_U64 is not supported"); } - // Write result (1-bit boolean) to destination switch (inst.dst[1].field) { case OperandField::VccLo: - return ir.SetVcc(result); + ir.SetVcc(cmp_result); + break; + case OperandField::ScalarGPR: - return ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), result); - case OperandField::VectorGPR: { - IR::Value result_val = ir.Select(result, ir.Imm32(1), ir.Imm32(0)); - ir.SetVectorReg(IR::VectorReg(inst.dst[1].code), IR::U32{result_val}); - } break; + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), cmp_result); + break; + default: - UNREACHABLE_MSG("Unsupported dst field: {}", u32(inst.dst[1].field)); + UNREACHABLE_MSG("Invalid V_CMP_U64 destination"); } } From 46ad72cecf0a98bc42db3536f44aa63991c9298f Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 17 Mar 2026 16:45:30 +0200 Subject: [PATCH 09/11] fixed vcclo --- src/shader_recompiler/frontend/translate/vector_alu.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 07a3e88da..80f525862 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -1164,8 +1164,7 @@ void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const case VccLo: // treat as 32-bit values, zero-extend to 64-bit { - IR::U32 val = ir.GetScalarReg(IR::ScalarReg(op.code)); - return {val, ir.Imm32(0)}; + return {ir.GetVccLo(), ir.GetVccHi()}; } default: UNREACHABLE_MSG("Unsupported operand field {}", u32(op.field)); From 80e1c296c541f62987f1f777375406817576b31e Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 17 Mar 2026 18:15:55 +0200 Subject: [PATCH 10/11] cleanup --- src/main.cpp | 148 +++--------------- .../frontend/translate/vector_alu.cpp | 5 +- 2 files changed, 19 insertions(+), 134 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index d2804ee62..cd319f8b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -71,136 +71,24 @@ int main(int argc, char* argv[]) { std::optional setAddonFolder; std::optional patchFile; - // ---- Options ---- - app.add_option("-g,--game", gamePath, "Game path or ID"); - app.add_option("-p,--patch", patchFile, "Patch file to apply"); - app.add_flag("-i,--ignore-game-patch", ignoreGamePatch, - "Disable automatic loading of game patches"); - - // FULLSCREEN: behavior-identical - app.add_option("-f,--fullscreen", fullscreenStr, "Fullscreen mode (true|false)"); - - app.add_option("--override-root", overrideRoot)->check(CLI::ExistingDirectory); - - app.add_flag("--wait-for-debugger", waitForDebugger); - app.add_option("--wait-for-pid", waitPid); - - app.add_flag("--show-fps", showFps); - app.add_flag("--config-clean", configClean); - app.add_flag("--config-global", configGlobal); - app.add_flag("--log-append", logAppend); - - app.add_option("--add-game-folder", addGameFolder)->check(CLI::ExistingDirectory); - app.add_option("--set-addon-folder", setAddonFolder)->check(CLI::ExistingDirectory); - - // ---- Capture args after `--` verbatim ---- - app.allow_extras(); - app.parse_complete_callback([&]() { - const auto& extras = app.remaining(); - if (!extras.empty()) { - gameArgs = extras; - } - }); - - // ---- No-args behavior ---- - if (argc == 1) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "shadPS4", - "This is a CLI application. Please use the QTLauncher for a GUI:\n" - "https://github.com/shadps4-emu/shadps4-qtlauncher/releases", - nullptr); - std::cout << app.help(); - return -1; - } - - try { - app.parse(argc, argv); - } catch (const CLI::ParseError& e) { - return app.exit(e); - } - - // ---- Utility commands ---- - if (addGameFolder) { - Config::addGameInstallDir(*addGameFolder); - Config::save(user_dir / "config.toml"); - std::cout << "Game folder successfully saved.\n"; - return 0; - } - - if (setAddonFolder) { - Config::setAddonInstallDir(*setAddonFolder); - Config::save(user_dir / "config.toml"); - std::cout << "Addon folder successfully saved.\n"; - return 0; - } - - if (!gamePath.has_value()) { - if (!gameArgs.empty()) { - gamePath = gameArgs.front(); - gameArgs.erase(gameArgs.begin()); - } else { - std::cerr << "Error: Please provide a game path or ID.\n"; - return 1; - } - } - if (!gameArgs.empty()) { - if (gameArgs.front() == "--") { - gameArgs.erase(gameArgs.begin()); - } else { - std::cerr << "Error: unhandled flags\n"; - return 1; - } - } - - // ---- Apply flags ---- - if (patchFile) - MemoryPatcher::patch_file = *patchFile; - - if (ignoreGamePatch) - Core::FileSys::MntPoints::ignore_game_patches = true; - - if (fullscreenStr) { - if (*fullscreenStr == "true") { - Config::setIsFullscreen(true); - } else if (*fullscreenStr == "false") { - Config::setIsFullscreen(false); - } else { - std::cerr << "Error: Invalid argument for --fullscreen (use true|false)\n"; - return 1; - } - } - - if (showFps) - Config::setShowFpsCounter(true); - - if (configClean) - Config::setConfigMode(Config::ConfigMode::Clean); - - if (configGlobal) - Config::setConfigMode(Config::ConfigMode::Global); - - if (logAppend) - Common::Log::SetAppend(); - - // ---- Resolve game path or ID ---- - std::filesystem::path ebootPath(*gamePath); - if (!std::filesystem::exists(ebootPath)) { - bool found = false; - constexpr int maxDepth = 5; - for (const auto& installDir : Config::getGameInstallDirs()) { - if (auto foundPath = Common::FS::FindGameByID(installDir, *gamePath, maxDepth)) { - ebootPath = *foundPath; - found = true; - break; - } - } - if (!found) { - std::cerr << "Error: Game ID or file path not found: " << *gamePath << "\n"; - return 1; - } - } - - if (waitPid) - Core::Debugger::WaitForPid(*waitPid); + // const char* const ebootPath = "M:/PS4/dumpedgames/CUSA00207/eboot.bin"; // bloodborne + // const char* const ebootPath = "D:/ps4/shadps4games/CUSA07010/eboot.bin"; // sonic mania + // const char* const ebootPath = "C:/ps4tests/CUSA03318/eboot.bin";//carmageddon + // const char* const ebootPath = "C:/ps4tests/CUSA04518/eboot.bin"; // project diva x + // const char* const ebootPath = "D:/ps4sdk/rain.elf"; + // const char* const ebootPath = "C:/ps4tests/CUSA05344/eboot.bin";//here we lie + // const char* const ebootPath = "C:/ps4tests/CUSA01499/eboot.bin"; // mirror's edge catalyst + // const char* const ebootPath = "D:/ps4/shadps4games/CUSA30582/eboot.bin";//SpongeBob + // SquarePants: The Cosmic Shake + //const char* const ebootPath = "D:/ps4/shadps4games/CUSA36843/eboot.bin"; // red dead + // const char* const ebootPath = "C:/ps4tests/CUSA00375/eboot.bin"; // the evil within + // const char* const ebootPath = "C:/ps4tests/CUSA51536/eboot.bin";//formula legends + // const char* const ebootPath = "D:/ps4/shadps4games/CUSA11616/eboot.bin"; // Reverie + // const char* const ebootPath = "C:/ps4tests/CUSA00093/eboot.bin";//driveclub + // const char* const ebootPath = "D:/ps4games/playable/CUSA05635/eboot.bin";//puyo tetris + //const char* const ebootPath = "M:/PS4/dumpedgames/CUSA07410/eboot.bin";//gow + //const char* const ebootPath = "D:/ps4/shadps4games/CUSA31498/eboot.bin";//street fighter 6 + const char* const ebootPath = "M:/PS4/dumpedgames/CUSA20099/eboot.bin";//gi joe auto* emulator = Common::Singleton::Instance(); emulator->executableName = argv[0]; diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 80f525862..199b6f7b0 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -1162,10 +1162,7 @@ void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const return {ir.Imm32(v), ir.Imm32(v < 0 ? -1 : 0)}; } case VccLo: - // treat as 32-bit values, zero-extend to 64-bit - { - return {ir.GetVccLo(), ir.GetVccHi()}; - } + return {ir.GetVccLo(), ir.GetVccHi()}; default: UNREACHABLE_MSG("Unsupported operand field {}", u32(op.field)); } From 20db2f721e1936eb8f3bdab6920bbdc0036174f8 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 18 Mar 2026 13:23:11 +0200 Subject: [PATCH 11/11] fixed main --- src/main.cpp | 148 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 130 insertions(+), 18 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index cd319f8b3..d2804ee62 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -71,24 +71,136 @@ int main(int argc, char* argv[]) { std::optional setAddonFolder; std::optional patchFile; - // const char* const ebootPath = "M:/PS4/dumpedgames/CUSA00207/eboot.bin"; // bloodborne - // const char* const ebootPath = "D:/ps4/shadps4games/CUSA07010/eboot.bin"; // sonic mania - // const char* const ebootPath = "C:/ps4tests/CUSA03318/eboot.bin";//carmageddon - // const char* const ebootPath = "C:/ps4tests/CUSA04518/eboot.bin"; // project diva x - // const char* const ebootPath = "D:/ps4sdk/rain.elf"; - // const char* const ebootPath = "C:/ps4tests/CUSA05344/eboot.bin";//here we lie - // const char* const ebootPath = "C:/ps4tests/CUSA01499/eboot.bin"; // mirror's edge catalyst - // const char* const ebootPath = "D:/ps4/shadps4games/CUSA30582/eboot.bin";//SpongeBob - // SquarePants: The Cosmic Shake - //const char* const ebootPath = "D:/ps4/shadps4games/CUSA36843/eboot.bin"; // red dead - // const char* const ebootPath = "C:/ps4tests/CUSA00375/eboot.bin"; // the evil within - // const char* const ebootPath = "C:/ps4tests/CUSA51536/eboot.bin";//formula legends - // const char* const ebootPath = "D:/ps4/shadps4games/CUSA11616/eboot.bin"; // Reverie - // const char* const ebootPath = "C:/ps4tests/CUSA00093/eboot.bin";//driveclub - // const char* const ebootPath = "D:/ps4games/playable/CUSA05635/eboot.bin";//puyo tetris - //const char* const ebootPath = "M:/PS4/dumpedgames/CUSA07410/eboot.bin";//gow - //const char* const ebootPath = "D:/ps4/shadps4games/CUSA31498/eboot.bin";//street fighter 6 - const char* const ebootPath = "M:/PS4/dumpedgames/CUSA20099/eboot.bin";//gi joe + // ---- Options ---- + app.add_option("-g,--game", gamePath, "Game path or ID"); + app.add_option("-p,--patch", patchFile, "Patch file to apply"); + app.add_flag("-i,--ignore-game-patch", ignoreGamePatch, + "Disable automatic loading of game patches"); + + // FULLSCREEN: behavior-identical + app.add_option("-f,--fullscreen", fullscreenStr, "Fullscreen mode (true|false)"); + + app.add_option("--override-root", overrideRoot)->check(CLI::ExistingDirectory); + + app.add_flag("--wait-for-debugger", waitForDebugger); + app.add_option("--wait-for-pid", waitPid); + + app.add_flag("--show-fps", showFps); + app.add_flag("--config-clean", configClean); + app.add_flag("--config-global", configGlobal); + app.add_flag("--log-append", logAppend); + + app.add_option("--add-game-folder", addGameFolder)->check(CLI::ExistingDirectory); + app.add_option("--set-addon-folder", setAddonFolder)->check(CLI::ExistingDirectory); + + // ---- Capture args after `--` verbatim ---- + app.allow_extras(); + app.parse_complete_callback([&]() { + const auto& extras = app.remaining(); + if (!extras.empty()) { + gameArgs = extras; + } + }); + + // ---- No-args behavior ---- + if (argc == 1) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "shadPS4", + "This is a CLI application. Please use the QTLauncher for a GUI:\n" + "https://github.com/shadps4-emu/shadps4-qtlauncher/releases", + nullptr); + std::cout << app.help(); + return -1; + } + + try { + app.parse(argc, argv); + } catch (const CLI::ParseError& e) { + return app.exit(e); + } + + // ---- Utility commands ---- + if (addGameFolder) { + Config::addGameInstallDir(*addGameFolder); + Config::save(user_dir / "config.toml"); + std::cout << "Game folder successfully saved.\n"; + return 0; + } + + if (setAddonFolder) { + Config::setAddonInstallDir(*setAddonFolder); + Config::save(user_dir / "config.toml"); + std::cout << "Addon folder successfully saved.\n"; + return 0; + } + + if (!gamePath.has_value()) { + if (!gameArgs.empty()) { + gamePath = gameArgs.front(); + gameArgs.erase(gameArgs.begin()); + } else { + std::cerr << "Error: Please provide a game path or ID.\n"; + return 1; + } + } + if (!gameArgs.empty()) { + if (gameArgs.front() == "--") { + gameArgs.erase(gameArgs.begin()); + } else { + std::cerr << "Error: unhandled flags\n"; + return 1; + } + } + + // ---- Apply flags ---- + if (patchFile) + MemoryPatcher::patch_file = *patchFile; + + if (ignoreGamePatch) + Core::FileSys::MntPoints::ignore_game_patches = true; + + if (fullscreenStr) { + if (*fullscreenStr == "true") { + Config::setIsFullscreen(true); + } else if (*fullscreenStr == "false") { + Config::setIsFullscreen(false); + } else { + std::cerr << "Error: Invalid argument for --fullscreen (use true|false)\n"; + return 1; + } + } + + if (showFps) + Config::setShowFpsCounter(true); + + if (configClean) + Config::setConfigMode(Config::ConfigMode::Clean); + + if (configGlobal) + Config::setConfigMode(Config::ConfigMode::Global); + + if (logAppend) + Common::Log::SetAppend(); + + // ---- Resolve game path or ID ---- + std::filesystem::path ebootPath(*gamePath); + if (!std::filesystem::exists(ebootPath)) { + bool found = false; + constexpr int maxDepth = 5; + for (const auto& installDir : Config::getGameInstallDirs()) { + if (auto foundPath = Common::FS::FindGameByID(installDir, *gamePath, maxDepth)) { + ebootPath = *foundPath; + found = true; + break; + } + } + if (!found) { + std::cerr << "Error: Game ID or file path not found: " << *gamePath << "\n"; + return 1; + } + } + + if (waitPid) + Core::Debugger::WaitForPid(*waitPid); auto* emulator = Common::Singleton::Instance(); emulator->executableName = argv[0];