From 56f72da1371f5a18af362a8aa012adb2e3bd2232 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Tue, 7 Jun 2022 21:26:34 +0300 Subject: [PATCH 1/4] [GPU] More exact PWL texture/RT gamma conversion --- src/xenia/gpu/dxbc_shader_translator.cc | 173 ++++++++++++------ src/xenia/gpu/dxbc_shader_translator.h | 26 ++- src/xenia/gpu/dxbc_shader_translator_fetch.cc | 4 +- src/xenia/gpu/dxbc_shader_translator_om.cc | 24 ++- src/xenia/gpu/xenos.cc | 85 +++++++++ src/xenia/gpu/xenos.h | 3 + 6 files changed, 238 insertions(+), 77 deletions(-) diff --git a/src/xenia/gpu/dxbc_shader_translator.cc b/src/xenia/gpu/dxbc_shader_translator.cc index 513e46887..1cf525b4c 100644 --- a/src/xenia/gpu/dxbc_shader_translator.cc +++ b/src/xenia/gpu/dxbc_shader_translator.cc @@ -212,63 +212,124 @@ void DxbcShaderTranslator::PopSystemTemp(uint32_t count) { system_temp_count_current_ -= std::min(count, system_temp_count_current_); } -void DxbcShaderTranslator::ConvertPWLGamma( - bool to_gamma, int32_t source_temp, uint32_t source_temp_component, - uint32_t target_temp, uint32_t target_temp_component, uint32_t piece_temp, - uint32_t piece_temp_component, uint32_t accumulator_temp, - uint32_t accumulator_temp_component) { - assert_true(source_temp != target_temp || - source_temp_component != target_temp_component || - ((target_temp != accumulator_temp || - target_temp_component != accumulator_temp_component) && - (target_temp != piece_temp || - target_temp_component != piece_temp_component))); - assert_true(piece_temp != source_temp || - piece_temp_component != source_temp_component); - assert_true(accumulator_temp != source_temp || - accumulator_temp_component != source_temp_component); - assert_true(piece_temp != accumulator_temp || - piece_temp_component != accumulator_temp_component); +void DxbcShaderTranslator::PWLGammaToLinear( + uint32_t target_temp, uint32_t target_temp_component, uint32_t source_temp, + uint32_t source_temp_component, bool source_pre_saturated, uint32_t temp1, + uint32_t temp1_component, uint32_t temp2, uint32_t temp2_component) { + // The source is needed only once to begin building the result, so it can be + // the same as the destination. + assert_true(temp1 != target_temp || temp1_component != target_temp_component); + assert_true(temp1 != source_temp || temp1_component != source_temp_component); + assert_true(temp2 != target_temp || temp2_component != target_temp_component); + assert_true(temp2 != source_temp || temp2_component != source_temp_component); + assert_true(temp1 != temp2 || temp1_component != temp2_component); + dxbc::Dest target_dest( + dxbc::Dest::R(target_temp, UINT32_C(1) << target_temp_component)); + dxbc::Src target_src(dxbc::Src::R(target_temp).Select(target_temp_component)); dxbc::Src source_src(dxbc::Src::R(source_temp).Select(source_temp_component)); - dxbc::Dest piece_dest(dxbc::Dest::R(piece_temp, 1 << piece_temp_component)); - dxbc::Src piece_src(dxbc::Src::R(piece_temp).Select(piece_temp_component)); - dxbc::Dest accumulator_dest( - dxbc::Dest::R(accumulator_temp, 1 << accumulator_temp_component)); - dxbc::Src accumulator_src( - dxbc::Src::R(accumulator_temp).Select(accumulator_temp_component)); - // For each piece: - // 1) Calculate how far we are on it. Multiply by 1/width, subtract - // start/width and saturate. - // 2) Add the contribution of the piece - multiply the position on the piece - // by its slope*width and accumulate. - // Piece 1. - a_.OpMul(piece_dest, source_src, - dxbc::Src::LF(to_gamma ? (1.0f / 0.0625f) : (1.0f / 0.25f)), true); - a_.OpMul(accumulator_dest, piece_src, - dxbc::Src::LF(to_gamma ? (4.0f * 0.0625f) : (0.25f * 0.25f))); - // Piece 2. - a_.OpMAd(piece_dest, source_src, - dxbc::Src::LF(to_gamma ? (1.0f / 0.0625f) : (1.0f / 0.125f)), - dxbc::Src::LF(to_gamma ? (-0.0625f / 0.0625f) : (-0.25f / 0.125f)), - true); - a_.OpMAd(accumulator_dest, piece_src, - dxbc::Src::LF(to_gamma ? (2.0f * 0.0625f) : (0.5f * 0.125f)), - accumulator_src); - // Piece 3. - a_.OpMAd(piece_dest, source_src, - dxbc::Src::LF(to_gamma ? (1.0f / 0.375f) : (1.0f / 0.375f)), - dxbc::Src::LF(to_gamma ? (-0.125f / 0.375f) : (-0.375f / 0.375f)), - true); - a_.OpMAd(accumulator_dest, piece_src, - dxbc::Src::LF(to_gamma ? (1.0f * 0.375f) : (1.0f * 0.375f)), - accumulator_src); - // Piece 4. - a_.OpMAd(piece_dest, source_src, - dxbc::Src::LF(to_gamma ? (1.0f / 0.5f) : (1.0f / 0.25f)), - dxbc::Src::LF(to_gamma ? (-0.5f / 0.5f) : (-0.75f / 0.25f)), true); - a_.OpMAd(dxbc::Dest::R(target_temp, 1 << target_temp_component), piece_src, - dxbc::Src::LF(to_gamma ? (0.5f * 0.5f) : (2.0f * 0.25f)), - accumulator_src); + dxbc::Dest temp1_dest(dxbc::Dest::R(temp1, UINT32_C(1) << temp1_component)); + dxbc::Src temp1_src(dxbc::Src::R(temp1).Select(temp1_component)); + dxbc::Dest temp2_dest(dxbc::Dest::R(temp2, UINT32_C(1) << temp2_component)); + dxbc::Src temp2_src(dxbc::Src::R(temp2).Select(temp2_component)); + + // Get the scale (into temp1) and the offset (into temp2) for the piece. + // Using `source >= threshold` comparisons because the input might have not + // been saturated yet, and thus it may be NaN - since it will be saturated to + // 0 later, the 0...64/255 case should be selected for it. + a_.OpGE(temp2_dest, source_src, dxbc::Src::LF(96.0f / 255.0f)); + a_.OpIf(true, temp2_src); + // [96/255 ... 1 + a_.OpGE(temp2_dest, source_src, dxbc::Src::LF(192.0f / 255.0f)); + a_.OpMovC(temp1_dest, temp2_src, dxbc::Src::LF(8.0f / 1024.0f), + dxbc::Src::LF(4.0f / 1024.0f)); + a_.OpMovC(temp2_dest, temp2_src, dxbc::Src::LF(-1024.0f), + dxbc::Src::LF(-256.0f)); + a_.OpElse(); + // 0 ... 96/255) + a_.OpGE(temp2_dest, source_src, dxbc::Src::LF(64.0f / 255.0f)); + a_.OpMovC(temp1_dest, temp2_src, dxbc::Src::LF(2.0f / 1024.0f), + dxbc::Src::LF(1.0f / 1024.0f)); + a_.OpMovC(temp2_dest, temp2_src, dxbc::Src::LF(-64.0f), dxbc::Src::LF(0.0f)); + a_.OpEndIf(); + + if (!source_pre_saturated) { + // Saturate the input, and flush NaN to 0. + a_.OpMov(target_dest, source_src, true); + } + // linear = gamma * (255 * 1024) * scale + offset + // As both 1024 and the scale are powers of 2, and 1024 * scale is not smaller + // than 1, it's not important if it's (gamma * 255) * 1024 * scale, + // (gamma * 255 * 1024) * scale, gamma * 255 * (1024 * scale), or + // gamma * (255 * 1024 * scale) - or the option chosen here, as long as + // 1024 is applied before the scale since the scale is < 1 (specifically at + // least 1/1024), and it may make very small values denormal. + a_.OpMul(target_dest, source_pre_saturated ? source_src : target_src, + dxbc::Src::LF(255.0f * 1024.0f)); + a_.OpMAd(target_dest, target_src, temp1_src, temp2_src); + // linear += trunc(linear * scale) + a_.OpMul(temp1_dest, target_src, temp1_src); + a_.OpRoundZ(temp1_dest, temp1_src); + a_.OpAdd(target_dest, target_src, temp1_src); + // linear *= 1/1023 + a_.OpMul(target_dest, target_src, dxbc::Src::LF(1.0f / 1023.0f)); +} + +void DxbcShaderTranslator::PreSaturatedLinearToPWLGamma( + uint32_t target_temp, uint32_t target_temp_component, uint32_t source_temp, + uint32_t source_temp_component, uint32_t temp_or_target, + uint32_t temp_or_target_component, uint32_t temp_non_target, + uint32_t temp_non_target_component) { + // The source may be the same as the target, but in this case it can't also be + // used as a temporary variable. + assert_true(target_temp != source_temp || + target_temp_component != source_temp_component || + target_temp != temp_or_target || + target_temp_component != temp_or_target_component); + assert_true(temp_or_target != source_temp || + temp_or_target_component != source_temp_component); + assert_true(temp_non_target != target_temp || + temp_non_target_component != target_temp_component); + assert_true(temp_non_target != source_temp || + temp_non_target_component != source_temp_component); + assert_true(temp_or_target != temp_non_target || + temp_or_target_component != temp_non_target_component); + dxbc::Dest target_dest( + dxbc::Dest::R(target_temp, UINT32_C(1) << target_temp_component)); + dxbc::Src target_src(dxbc::Src::R(target_temp).Select(target_temp_component)); + dxbc::Src source_src(dxbc::Src::R(source_temp).Select(source_temp_component)); + dxbc::Dest temp_or_target_dest( + dxbc::Dest::R(temp_or_target, UINT32_C(1) << temp_or_target_component)); + dxbc::Src temp_or_target_src( + dxbc::Src::R(temp_or_target).Select(temp_or_target_component)); + dxbc::Dest temp_non_target_dest( + dxbc::Dest::R(temp_non_target, UINT32_C(1) << temp_non_target_component)); + dxbc::Src temp_non_target_src( + dxbc::Src::R(temp_non_target).Select(temp_non_target_component)); + + // Get the scale (into temp_or_target) and the offset (into temp_non_target) + // for the piece. + a_.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(128.0f / 1023.0f)); + a_.OpIf(true, temp_non_target_src); + // [128/1023 ... 1 + a_.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(512.0f / 1023.0f)); + a_.OpMovC(temp_or_target_dest, temp_non_target_src, + dxbc::Src::LF(1023.0f / 8.0f), dxbc::Src::LF(1023.0f / 4.0f)); + a_.OpMovC(temp_non_target_dest, temp_non_target_src, + dxbc::Src::LF(128.0f / 255.0f), dxbc::Src::LF(64.0f / 255.0f)); + a_.OpElse(); + // 0 ... 128/1023) + a_.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(64.0f / 1023.0f)); + a_.OpMovC(temp_or_target_dest, temp_non_target_src, + dxbc::Src::LF(1023.0f / 2.0f), dxbc::Src::LF(1023.0f)); + a_.OpMovC(temp_non_target_dest, temp_non_target_src, + dxbc::Src::LF(32.0f / 255.0f), dxbc::Src::LF(0.0f)); + a_.OpEndIf(); + + // gamma = trunc(linear * scale) * (1.0 / 255.0) + offset + a_.OpMul(target_dest, source_src, temp_or_target_src); + a_.OpRoundZ(target_dest, target_src); + a_.OpMAd(target_dest, target_src, dxbc::Src::LF(1.0f / 255.0f), + temp_non_target_src); } void DxbcShaderTranslator::RemapAndConvertVertexIndices( diff --git a/src/xenia/gpu/dxbc_shader_translator.h b/src/xenia/gpu/dxbc_shader_translator.h index 726f96cc2..6b78310e8 100644 --- a/src/xenia/gpu/dxbc_shader_translator.h +++ b/src/xenia/gpu/dxbc_shader_translator.h @@ -664,15 +664,23 @@ class DxbcShaderTranslator : public ShaderTranslator { // Frees the last allocated internal r# registers for later reuse. void PopSystemTemp(uint32_t count = 1); - // Converts one scalar to or from PWL gamma, using 1 temporary scalar. - // The target may be the same as any of the source, the piece temporary or the - // accumulator, but not two or three of these. - // The piece and the accumulator can't be the same as source or as each other. - void ConvertPWLGamma(bool to_gamma, int32_t source_temp, - uint32_t source_temp_component, uint32_t target_temp, - uint32_t target_temp_component, uint32_t piece_temp, - uint32_t piece_temp_component, uint32_t accumulator_temp, - uint32_t accumulator_temp_component); + // Converts one scalar from piecewise linear gamma to linear. The target may + // be the same as the source, the temporary variables must be different. If + // the source is not pre-saturated, saturation will be done internally. + void PWLGammaToLinear(uint32_t target_temp, uint32_t target_temp_component, + uint32_t source_temp, uint32_t source_temp_component, + bool source_pre_saturated, uint32_t temp1, + uint32_t temp1_component, uint32_t temp2, + uint32_t temp2_component); + // Converts one scalar, which must be saturated before calling this function, + // from linear to piecewise linear gamma. The target may be the same as either + // the source or as temp_or_target, but not as both (and temp_or_target may + // not be the same as the source). temp_non_target must be different. + void PreSaturatedLinearToPWLGamma( + uint32_t target_temp, uint32_t target_temp_component, + uint32_t source_temp, uint32_t source_temp_component, + uint32_t temp_or_target, uint32_t temp_or_target_component, + uint32_t temp_non_target, uint32_t temp_non_target_component); bool IsSampleRate() const { assert_true(is_pixel_shader()); diff --git a/src/xenia/gpu/dxbc_shader_translator_fetch.cc b/src/xenia/gpu/dxbc_shader_translator_fetch.cc index 4a84119f8..7716c4a26 100644 --- a/src/xenia/gpu/dxbc_shader_translator_fetch.cc +++ b/src/xenia/gpu/dxbc_shader_translator_fetch.cc @@ -2103,8 +2103,8 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction( a_.OpIf(false, dxbc::Src::R(gamma_temp, dxbc::Src::kXXXX)); } // Convert from piecewise linear. - ConvertPWLGamma(false, system_temp_result_, i, system_temp_result_, i, - gamma_temp, 0, gamma_temp, 1); + PWLGammaToLinear(system_temp_result_, i, system_temp_result_, i, false, + gamma_temp, 0, gamma_temp, 1); if (gamma_render_target_as_srgb_) { a_.OpElse(); // Convert from sRGB. diff --git a/src/xenia/gpu/dxbc_shader_translator_om.cc b/src/xenia/gpu/dxbc_shader_translator_om.cc index 685911285..eb5a8bd38 100644 --- a/src/xenia/gpu/dxbc_shader_translator_om.cc +++ b/src/xenia/gpu/dxbc_shader_translator_om.cc @@ -1384,8 +1384,8 @@ void DxbcShaderTranslator::ROV_UnpackColor( dxbc::Src::LF(1.0f / 255.0f)); if (i) { for (uint32_t j = 0; j < 3; ++j) { - ConvertPWLGamma(false, color_temp, j, color_temp, j, temp1, - temp1_component, temp2, temp2_component); + PWLGammaToLinear(color_temp, j, color_temp, j, true, temp1, + temp1_component, temp2, temp2_component); } } a_.OpBreak(); @@ -1537,8 +1537,9 @@ void DxbcShaderTranslator::ROV_PackPreClampedColor( : xenos::ColorRenderTargetFormat::k_8_8_8_8))); for (uint32_t j = 0; j < 4; ++j) { if (i && j < 3) { - ConvertPWLGamma(true, color_temp, j, temp1, temp1_component, temp1, - temp1_component, temp2, temp2_component); + PreSaturatedLinearToPWLGamma(temp1, temp1_component, color_temp, j, + temp1, temp1_component, temp2, + temp2_component); // Denormalize and add 0.5 for rounding. a_.OpMAd(temp1_dest, temp1_src, dxbc::Src::LF(255.0f), dxbc::Src::LF(0.5f)); @@ -1863,10 +1864,10 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToRTVs() { if (!(shader_writes_color_targets & (1 << i))) { continue; } + uint32_t system_temp_color = system_temps_color_[i]; // Apply the exponent bias after alpha to coverage because it needs the - // unbiased alpha from the shader - a_.OpMul(dxbc::Dest::R(system_temps_color_[i]), - dxbc::Src::R(system_temps_color_[i]), + // unbiased alpha from the shader. + a_.OpMul(dxbc::Dest::R(system_temp_color), dxbc::Src::R(system_temp_color), LoadSystemConstant( SystemConstants::Index::kColorExpBias, offsetof(SystemConstants, color_exp_bias) + sizeof(float) * i, @@ -1878,14 +1879,17 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToRTVs() { a_.OpAnd(dxbc::Dest::R(gamma_temp, 0b0001), LoadFlagsSystemConstant(), dxbc::Src::LU(kSysFlag_ConvertColor0ToGamma << i)); a_.OpIf(true, dxbc::Src::R(gamma_temp, dxbc::Src::kXXXX)); + // Saturate before the gamma conversion. + a_.OpMov(dxbc::Dest::R(system_temp_color, 0b0111), + dxbc::Src::R(system_temp_color), true); for (uint32_t j = 0; j < 3; ++j) { - ConvertPWLGamma(true, system_temps_color_[i], j, system_temps_color_[i], - j, gamma_temp, 0, gamma_temp, 1); + PreSaturatedLinearToPWLGamma(system_temp_color, j, system_temp_color, j, + gamma_temp, 0, gamma_temp, 1); } a_.OpEndIf(); } // Copy the color from a readable temp register to an output register. - a_.OpMov(dxbc::Dest::O(i), dxbc::Src::R(system_temps_color_[i])); + a_.OpMov(dxbc::Dest::O(i), dxbc::Src::R(system_temp_color)); } // Release gamma_temp. PopSystemTemp(); diff --git a/src/xenia/gpu/xenos.cc b/src/xenia/gpu/xenos.cc index 4d4a279b5..397ba3b24 100644 --- a/src/xenia/gpu/xenos.cc +++ b/src/xenia/gpu/xenos.cc @@ -17,6 +17,91 @@ namespace xe { namespace gpu { namespace xenos { +// Based on X360GammaToLinear and X360LinearToGamma from the Source Engine, with +// additional logic from Direct3D 9 code in game executable disassembly, located +// via the floating-point constants involved. +// https://github.com/ValveSoftware/source-sdk-2013/blob/master/mp/src/mathlib/color_conversion.cpp#L329 +// These are provided here in part as a reference for shader translators. + +float PWLGammaToLinear(float gamma) { + // Not found in game executables, so just using the logic similar to that in + // the Source Engine. + gamma = xe::saturate_unsigned(gamma); + float scale, offset; + // While the compiled code for linear to gamma conversion uses `vcmpgtfp + // constant, value` comparison (constant > value, or value < constant), it's + // preferable to use `value >= constant` condition for the higher pieces, as + // it will never pass for NaN, and in case of NaN, the 0...64/255 case will be + // selected regardless of whether it's saturated before or after the + // comparisons (always pre-saturating here, but shader translators may choose + // to saturate later for convenience), as saturation will flush NaN to 0. + if (gamma >= 96.0f / 255.0f) { + if (gamma >= 192.0f / 255.0f) { + scale = 8.0f / 1024.0f; + offset = -1024.0f; + } else { + scale = 4.0f / 1024.0f; + offset = -256.0f; + } + } else { + if (gamma >= 64.0f / 255.0f) { + scale = 2.0f / 1024.0f; + offset = -64.0f; + } else { + scale = 1.0f / 1024.0f; + offset = 0.0f; + // No `floor` term in this case in the Source Engine, but for the largest + // value, 1.0, `floor(255.0f * (1.0f / 1024.0f))` is 0 anyway. + } + } + // Though in the Source Engine, the 1/1024 multiplication is done for the + // truncated part specifically, pre-baking it into the scale is lossless - + // both 1024 and `scale` are powers of 2. + float linear = gamma * ((255.0f * 1024.0f) * scale) + offset; + // For consistency with linear to gamma, and because it's more logical here + // (0 rather than 1 at -epsilon), using `trunc` instead of `floor`. + linear += std::trunc(linear * scale); + linear *= 1.0f / 1023.0f; + // Clamping is not necessary (1 * (255 * 8) - 1024 + 7 is exactly 1023). + return linear; +} + +float LinearToPWLGamma(float linear) { + linear = xe::saturate_unsigned(linear); + float scale, offset; + // While the compiled code uses `vcmpgtfp constant, value` comparison + // (constant > value, or value < constant), it's preferable to use `value >= + // constant` condition for the higher pieces, as it will never pass for NaN, + // and in case of NaN, the 0...64/1023 case will be selected regardless of + // whether it's saturated before or after the comparisons (always + // pre-saturating here, but shader translators may choose to saturate later + // for convenience), as saturation will flush NaN to 0. + if (linear >= 128.0f / 1023.0f) { + if (linear >= 512.0f / 1023.0f) { + scale = 1023.0f / 8.0f; + offset = 128.0f / 255.0f; + } else { + scale = 1023.0f / 4.0f; + offset = 64.0f / 255.0f; + } + } else { + if (linear >= 64.0f / 1023.0f) { + scale = 1023.0f / 2.0f; + offset = 32.0f / 255.0f; + } else { + scale = 1023.0f; + offset = 0.0f; + } + } + // The truncation isn't in X360LinearToGamma in the Source Engine, but is + // there in Direct3D 9 disassembly (the `vrfiz` instructions). + // It also prevents conversion of 1.0 to 1.0034313725490196078431372549016 + // that's handled via clamping in the Source Engine. + // 127.875 (1023 / 8) is truncated to 127, which, after scaling, becomes + // 127 / 255, and when 128 / 255 is added, the result is 1. + return std::trunc(linear * scale) * (1.0f / 255.0f) + offset; +} + // https://github.com/Microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexConvert.cpp float Float7e3To32(uint32_t f10) { diff --git a/src/xenia/gpu/xenos.h b/src/xenia/gpu/xenos.h index 97c83639e..c4e0870d6 100644 --- a/src/xenia/gpu/xenos.h +++ b/src/xenia/gpu/xenos.h @@ -327,6 +327,9 @@ enum class DepthRenderTargetFormat : uint32_t { const char* GetDepthRenderTargetFormatName(DepthRenderTargetFormat format); +float PWLGammaToLinear(float gamma); +float LinearToPWLGamma(float linear); + // Converts Xenos floating-point 7e3 color value in bits 0:9 (not clamping) to // an IEEE-754 32-bit floating-point number. float Float7e3To32(uint32_t f10); From 78d1eb8bf82ec621587712c44f7c0f7c6c9ad54c Mon Sep 17 00:00:00 2001 From: Triang3l Date: Thu, 9 Jun 2022 21:34:21 +0300 Subject: [PATCH 2/4] [GPU] TextureCache::GetActiveTextureHostSwizzle --- src/xenia/gpu/texture_cache.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/xenia/gpu/texture_cache.h b/src/xenia/gpu/texture_cache.h index b2c5b1d60..48a54da38 100644 --- a/src/xenia/gpu/texture_cache.h +++ b/src/xenia/gpu/texture_cache.h @@ -98,8 +98,11 @@ class TextureCache { // "ActiveTexture" means as of the latest RequestTextures call. - // Returns the post-swizzle signedness of a currently bound texture (must be - // called after RequestTextures). + uint8_t GetActiveTextureHostSwizzle(uint32_t fetch_constant_index) const { + const TextureBinding* binding = + GetValidTextureBinding(fetch_constant_index); + return binding ? binding->host_swizzle : xenos::XE_GPU_TEXTURE_SWIZZLE_0000; + } uint8_t GetActiveTextureSwizzledSigns(uint32_t fetch_constant_index) const { const TextureBinding* binding = GetValidTextureBinding(fetch_constant_index); From 945976a31d91a63a97446f0020570280457bd1dc Mon Sep 17 00:00:00 2001 From: Gliniak Date: Fri, 2 Oct 2020 22:33:42 +0200 Subject: [PATCH 3/4] Added Premake Files For PatchingSystem --- premake5.lua | 1 + src/xenia/app/premake5.lua | 1 + src/xenia/gpu/d3d12/premake5.lua | 2 ++ src/xenia/gpu/vulkan/premake5.lua | 2 ++ src/xenia/patcher/premake5.lua | 14 ++++++++++++++ 5 files changed, 20 insertions(+) create mode 100644 src/xenia/patcher/premake5.lua diff --git a/premake5.lua b/premake5.lua index 449879afa..3659c683a 100644 --- a/premake5.lua +++ b/premake5.lua @@ -286,6 +286,7 @@ workspace("xenia") include("src/xenia/hid") include("src/xenia/hid/nop") include("src/xenia/kernel") + include("src/xenia/patcher") include("src/xenia/ui") include("src/xenia/ui/spirv") include("src/xenia/ui/vulkan") diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua index 64ebf0db2..f68af7404 100644 --- a/src/xenia/app/premake5.lua +++ b/src/xenia/app/premake5.lua @@ -25,6 +25,7 @@ project("xenia-app") "xenia-hid-nop", "xenia-hid-sdl", "xenia-kernel", + "xenia-patcher", "xenia-ui", "xenia-ui-spirv", "xenia-ui-vulkan", diff --git a/src/xenia/gpu/d3d12/premake5.lua b/src/xenia/gpu/d3d12/premake5.lua index bc6e95653..9dd896b8e 100644 --- a/src/xenia/gpu/d3d12/premake5.lua +++ b/src/xenia/gpu/d3d12/premake5.lua @@ -36,6 +36,7 @@ project("xenia-gpu-d3d12-trace-viewer") "xenia-hid", "xenia-hid-nop", "xenia-kernel", + "xenia-patcher", "xenia-ui", "xenia-ui-d3d12", "xenia-vfs", @@ -86,6 +87,7 @@ project("xenia-gpu-d3d12-trace-dump") "xenia-ui", "xenia-ui-d3d12", "xenia-vfs", + "xenia-patcher", }) links({ "aes_128", diff --git a/src/xenia/gpu/vulkan/premake5.lua b/src/xenia/gpu/vulkan/premake5.lua index a95f8f492..6f4879b66 100644 --- a/src/xenia/gpu/vulkan/premake5.lua +++ b/src/xenia/gpu/vulkan/premake5.lua @@ -41,6 +41,7 @@ project("xenia-gpu-vulkan-trace-viewer") "xenia-hid", "xenia-hid-nop", "xenia-kernel", + "xenia-patcher", "xenia-ui", "xenia-ui-spirv", "xenia-ui-vulkan", @@ -112,6 +113,7 @@ project("xenia-gpu-vulkan-trace-dump") "xenia-ui-spirv", "xenia-ui-vulkan", "xenia-vfs", + "xenia-patcher", }) links({ "aes_128", diff --git a/src/xenia/patcher/premake5.lua b/src/xenia/patcher/premake5.lua new file mode 100644 index 000000000..ac8f255c4 --- /dev/null +++ b/src/xenia/patcher/premake5.lua @@ -0,0 +1,14 @@ +project_root = "../../.." +include(project_root.."/tools/build") + +group("src") +project("xenia-patcher") + uuid("e1c75f76-9e7b-48f6-b17e-dbd20f7a1592") + kind("StaticLib") + language("C++") + links({ + "xenia-base" + }) + defines({ + }) + recursive_platform_files() From 91f43a374d52248a28653945b2db2ebaed837536 Mon Sep 17 00:00:00 2001 From: Gliniak Date: Fri, 9 Oct 2020 21:02:32 +0200 Subject: [PATCH 4/4] Initial support for xex patching --- src/xenia/app/emulator_window.cc | 4 + src/xenia/app/xenia_main.cc | 4 + src/xenia/base/string_util.h | 26 ++++ src/xenia/cpu/xex_module.cc | 2 - src/xenia/emulator.cc | 2 + src/xenia/emulator.h | 5 + src/xenia/kernel/kernel_state.cc | 3 + src/xenia/kernel/user_module.cc | 45 +++++++ src/xenia/kernel/user_module.h | 3 + src/xenia/patcher/patch_db.cc | 223 +++++++++++++++++++++++++++++++ src/xenia/patcher/patch_db.h | 132 ++++++++++++++++++ src/xenia/patcher/patcher.cc | 66 +++++++++ src/xenia/patcher/patcher.h | 36 +++++ 13 files changed, 549 insertions(+), 2 deletions(-) create mode 100644 src/xenia/patcher/patch_db.cc create mode 100644 src/xenia/patcher/patch_db.h create mode 100644 src/xenia/patcher/patcher.cc create mode 100644 src/xenia/patcher/patcher.h diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 7e6d9e6b7..fb60e3057 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -1004,6 +1004,10 @@ void EmulatorWindow::UpdateTitle() { sb.Append(u8" (Preloading shaders\u2026)"); } + patcher::Patcher* patcher = emulator()->patcher(); + if (patcher && patcher->IsAnyPatchApplied()) { + sb.Append(u8" [Patches Applied]"); + } window_->SetTitle(sb.to_string_view()); } diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index ed6f1e9f1..a4f4fbcb0 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -509,6 +509,10 @@ void EmulatorApp::EmulatorThread() { }); }); + emulator_->on_patch_apply.AddListener([this]() { + app_context().CallInUIThread([this]() { emulator_window_->UpdateTitle(); }); + }); + emulator_->on_terminate.AddListener([]() { if (cvars::discord) { discord::DiscordPresence::NotPlaying(); diff --git a/src/xenia/base/string_util.h b/src/xenia/base/string_util.h index 09575ff56..4cbbcc6b3 100644 --- a/src/xenia/base/string_util.h +++ b/src/xenia/base/string_util.h @@ -111,6 +111,32 @@ inline size_t copy_and_swap_maybe_truncating(char16_t* dest, return chars_copied; } +inline bool hex_string_to_array(std::vector& output_array, + const std::string_view value) { + output_array.reserve((value.size() + 1) / 2); + + size_t remaining_length = value.size(); + while (remaining_length > 0) { + uint8_t chars_to_read = remaining_length > 1 ? 2 : 1; + const char* substring_pointer = + value.data() + value.size() - remaining_length; + + uint8_t string_value = 0; + std::from_chars_result result = std::from_chars( + substring_pointer, substring_pointer + chars_to_read, string_value, 16); + + if (result.ec != std::errc() || + result.ptr != substring_pointer + chars_to_read) { + output_array.clear(); + return false; + } + + output_array.push_back(string_value); + remaining_length -= chars_to_read; + } + return true; +} + inline std::string to_hex_string(uint32_t value) { return fmt::format("{:08X}", value); } diff --git a/src/xenia/cpu/xex_module.cc b/src/xenia/cpu/xex_module.cc index 672d44666..b0b963467 100644 --- a/src/xenia/cpu/xex_module.cc +++ b/src/xenia/cpu/xex_module.cc @@ -930,8 +930,6 @@ bool XexModule::Load(const std::string_view name, const std::string_view path, name_ = name; path_ = path; - uint8_t* data = memory()->TranslateVirtual(base_address_); - // Load in the XEX basefile // We'll try using both XEX2 keys to see if any give a valid PE int result_code = ReadImage(xex_addr, xex_length, false); diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 0fbf74d18..d0a7861b7 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -219,6 +219,8 @@ X_STATUS Emulator::Setup( // Bring up the virtual filesystem used by the kernel. file_system_ = std::make_unique(); + patcher_ = std::make_unique(storage_root_); + // Shared kernel state. kernel_state_ = std::make_unique(this); diff --git a/src/xenia/emulator.h b/src/xenia/emulator.h index 96d81d00a..0e3b9164b 100644 --- a/src/xenia/emulator.h +++ b/src/xenia/emulator.h @@ -21,6 +21,7 @@ #include "xenia/base/exception_handler.h" #include "xenia/kernel/kernel_state.h" #include "xenia/memory.h" +#include "xenia/patcher/patcher.h" #include "xenia/vfs/virtual_file_system.h" #include "xenia/xbox.h" @@ -154,6 +155,8 @@ class Emulator { // This is effectively the guest operating system. kernel::KernelState* kernel_state() const { return kernel_state_.get(); } + patcher::Patcher* patcher() const { return patcher_.get(); } + // Initializes the emulator and configures all components. // The given window is used for display and the provided functions are used // to create subsystems as required. @@ -202,6 +205,7 @@ class Emulator { public: xe::Delegate on_launch; xe::Delegate on_shader_storage_initialization; + xe::Delegate<> on_patch_apply; xe::Delegate<> on_terminate; xe::Delegate<> on_exit; @@ -237,6 +241,7 @@ class Emulator { std::unique_ptr export_resolver_; std::unique_ptr file_system_; + std::unique_ptr patcher_; std::unique_ptr kernel_state_; diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 7d60aac64..8c514eb17 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -406,6 +406,9 @@ object_ref KernelState::LoadUserModule( } module->Dump(); + emulator_->patcher()->ApplyPatchesForTitle(memory_, module->title_id(), + module->hash()); + emulator_->on_patch_apply(); if (module->is_dll_module() && module->entry_point() && call_entry) { // Call DllMain(DLL_PROCESS_ATTACH): diff --git a/src/xenia/kernel/user_module.cc b/src/xenia/kernel/user_module.cc index a85a919c6..8bb5616fe 100644 --- a/src/xenia/kernel/user_module.cc +++ b/src/xenia/kernel/user_module.cc @@ -13,6 +13,7 @@ #include "xenia/base/byte_stream.h" #include "xenia/base/logging.h" +#include "xenia/base/xxhash.h" #include "xenia/cpu/elf_module.h" #include "xenia/cpu/processor.h" #include "xenia/cpu/xex_module.h" @@ -128,6 +129,9 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) { } } + CalculateHash(); + + XELOGI("Module hash: {:016X} for {}", hash_.value_or(UINT64_MAX), name_); return LoadXexContinue(); } @@ -804,5 +808,46 @@ void UserModule::Dump() { xe::logging::AppendLogLine(xe::LogLevel::Info, 'i', sb.to_string_view()); } +void UserModule::CalculateHash() { + const BaseHeap* module_heap = + kernel_state_->memory()->LookupHeap(xex_module()->base_address()); + + if (!module_heap) { + XELOGE("Invalid heap for xex module! Address: {:08X}", + xex_module()->base_address()); + return; + } + + const uint32_t page_size = module_heap->page_size(); + auto security_info = xex_module()->xex_security_info(); + + auto find_code_section_page = [&security_info](bool from_bottom) { + for (uint32_t i = 0; i < security_info->page_descriptor_count; i++) { + const uint32_t page_index = + from_bottom ? i : security_info->page_descriptor_count - i; + xex2_page_descriptor page_descriptor; + page_descriptor.value = + xe::byte_swap(security_info->page_descriptors[page_index].value); + if (page_descriptor.info != XEX_SECTION_CODE) { + continue; + } + return page_index; + } + return UINT32_MAX; + }; + + const uint32_t start_address = + xex_module()->base_address() + (find_code_section_page(true) * page_size); + const uint32_t end_address = + xex_module()->base_address() + + ((find_code_section_page(false) + 1) * page_size); + + uint8_t* base_code_adr = memory()->TranslateVirtual(start_address); + + XXH3_state_t hash_state; + XXH3_64bits_reset(&hash_state); + XXH3_64bits_update(&hash_state, base_code_adr, end_address - start_address); + hash_ = XXH3_64bits_digest(&hash_state); +} } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/user_module.h b/src/xenia/kernel/user_module.h index f904f8a4f..d78cc79bd 100644 --- a/src/xenia/kernel/user_module.h +++ b/src/xenia/kernel/user_module.h @@ -38,6 +38,7 @@ class UserModule : public XModule { const std::string& path() const override { return path_; } const std::string& name() const override { return name_; } + std::optional hash() const { return hash_; } enum ModuleFormat { kModuleFormatUndefined = 0, @@ -97,9 +98,11 @@ class UserModule : public XModule { private: X_STATUS LoadXexContinue(); + void CalculateHash(); std::string name_; std::string path_; + std::optional hash_ = std::nullopt; uint32_t guest_xex_header_ = 0; ModuleFormat module_format_ = kModuleFormatUndefined; diff --git a/src/xenia/patcher/patch_db.cc b/src/xenia/patcher/patch_db.cc new file mode 100644 index 000000000..6ebe48e6a --- /dev/null +++ b/src/xenia/patcher/patch_db.cc @@ -0,0 +1,223 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ +#include "xenia/base/cvar.h" +#include "xenia/base/filesystem.h" +#include "xenia/base/logging.h" +#include "xenia/memory.h" + +#include "xenia/patcher/patch_db.h" + +DEFINE_bool(apply_patches, true, "Enables custom patching functionality", + "General"); + +namespace xe { +namespace patcher { + +PatchDB::PatchDB(const std::filesystem::path patches_root) { + patches_root_ = patches_root; + LoadPatches(); +} + +PatchDB::~PatchDB() {} + +void PatchDB::LoadPatches() { + if (!cvars::apply_patches) { + return; + } + + const std::filesystem::path patches_directory = patches_root_ / "patches"; + const std::vector patch_files = + filesystem::ListFiles(patches_directory); + + for (const xe::filesystem::FileInfo& patch_file : patch_files) { + // Skip files that doesn't have only title_id as name and .patch as + // extension + if (!std::regex_match(path_to_utf8(patch_file.name), + patch_filename_regex_)) { + XELOGE("PatchDB: Skipped loading file {} due to incorrect filename", + path_to_utf8(patch_file.name)); + continue; + } + + const PatchFileEntry loaded_title_patches = + ReadPatchFile(patch_file.path / patch_file.name); + if (loaded_title_patches.title_id != -1) { + loaded_patches_.push_back(loaded_title_patches); + } + } + XELOGI("PatchDB: Loaded patches for {} titles", loaded_patches_.size()); +} + +PatchFileEntry PatchDB::ReadPatchFile(const std::filesystem::path& file_path) { + PatchFileEntry patch_file; + std::shared_ptr patch_toml_fields; + + try { + patch_toml_fields = cpptoml::parse_file(path_to_utf8(file_path)); + } catch (...) { + XELOGE("PatchDB: Cannot load patch file: {}", path_to_utf8(file_path)); + patch_file.title_id = -1; + return patch_file; + }; + + auto title_name = patch_toml_fields->get_as("title_name"); + auto title_id = patch_toml_fields->get_as("title_id"); + + patch_file.title_id = strtoul((*title_id).c_str(), NULL, 16); + patch_file.title_name = *title_name; + ReadHashes(patch_file, patch_toml_fields); + + auto patch_table = patch_toml_fields->get_table_array("patch"); + + for (auto patch_table_entry : *patch_table) { + PatchInfoEntry patch = PatchInfoEntry(); + auto patch_name = *patch_table_entry->get_as("name"); + auto patch_desc = *patch_table_entry->get_as("desc"); + auto patch_author = *patch_table_entry->get_as("author"); + auto is_enabled = *patch_table_entry->get_as("is_enabled"); + + patch.id = 0; // Todo(Gliniak): Implement id for future GUI stuff + patch.patch_name = patch_name; + patch.patch_desc = patch_desc; + patch.patch_author = patch_author; + patch.is_enabled = is_enabled; + + // Iterate through all available data sizes + for (const auto& patch_data_type : patch_data_types_size_) { + bool success = + ReadPatchData(patch.patch_data, patch_data_type, patch_table_entry); + + if (!success) { + XELOGE("PatchDB: Cannot read patch {}", patch_name); + break; + } + } + patch_file.patch_info.push_back(patch); + } + return patch_file; +} + +bool PatchDB::ReadPatchData( + std::vector& patch_data, + const std::pair data_type, + const std::shared_ptr& patch_table) { + auto patch_data_tarr = patch_table->get_table_array(data_type.first); + if (!patch_data_tarr) { + return true; + } + + for (const auto& patch_data_table : *patch_data_tarr) { + uint32_t address = *patch_data_table->get_as("address"); + size_t alloc_size = (size_t)data_type.second.size; + + switch (data_type.second.type) { + case PatchDataType::kBE8: { + uint16_t value = *patch_data_table->get_as("value"); + patch_data.push_back({address, PatchDataValue(alloc_size, value)}); + break; + } + case PatchDataType::kBE16: { + uint16_t value = *patch_data_table->get_as("value"); + patch_data.push_back( + {address, PatchDataValue(alloc_size, xe::byte_swap(value))}); + break; + } + case PatchDataType::kBE32: { + uint32_t value = *patch_data_table->get_as("value"); + patch_data.push_back( + {address, PatchDataValue(alloc_size, xe::byte_swap(value))}); + break; + } + case PatchDataType::kBE64: { + uint64_t value = *patch_data_table->get_as("value"); + patch_data.push_back( + {address, PatchDataValue(alloc_size, xe::byte_swap(value))}); + break; + } + case PatchDataType::kF64: { + double val = *patch_data_table->get_as("value"); + uint64_t value = *reinterpret_cast(&val); + patch_data.push_back( + {address, PatchDataValue(alloc_size, xe::byte_swap(value))}); + break; + } + case PatchDataType::kF32: { + float value = float(*patch_data_table->get_as("value")); + patch_data.push_back( + {address, PatchDataValue(alloc_size, xe::byte_swap(value))}); + break; + } + case PatchDataType::kString: { + std::string value = *patch_data_table->get_as("value"); + patch_data.push_back({address, PatchDataValue(value)}); + break; + } + case PatchDataType::kU16String: { + std::u16string value = + xe::to_utf16(*patch_data_table->get_as("value")); + patch_data.push_back({address, PatchDataValue(value)}); + break; + } + case PatchDataType::kByteArray: { + std::vector data; + const std::string value = + *patch_data_table->get_as("value"); + + bool success = string_util::hex_string_to_array(data, value); + if (!success) { + XELOGW("PatchDB: Cannot convert hex string to byte array! Skipping", + address); + return false; + } + patch_data.push_back({address, PatchDataValue(data)}); + break; + } + default: { + XELOGW("PatchDB: Unknown patch data type for address {:08X}! Skipping", + address); + return false; + } + } + } + return true; +} + +std::vector PatchDB::GetTitlePatches( + const uint32_t title_id, const std::optional hash) { + std::vector title_patches; + + std::copy_if( + loaded_patches_.cbegin(), loaded_patches_.cend(), + std::back_inserter(title_patches), [=](const PatchFileEntry entry) { + bool hash_exist = std::find(entry.hashes.cbegin(), entry.hashes.cend(), + hash) != entry.hashes.cend(); + + return entry.title_id == title_id && + (entry.hashes.empty() || hash_exist); + }); + + return title_patches; +} + +void PatchDB::ReadHashes(PatchFileEntry& patch_entry, + std::shared_ptr patch_toml_fields) { + auto title_hashes = patch_toml_fields->get_array_of("hash"); + + for (const auto& hash : *title_hashes) { + patch_entry.hashes.push_back(strtoull(hash.c_str(), NULL, 16)); + } + + auto single_hash = patch_toml_fields->get_as("hash"); + if (single_hash) { + patch_entry.hashes.push_back(strtoull((*single_hash).c_str(), NULL, 16)); + } +} + +} // namespace patcher +} // namespace xe diff --git a/src/xenia/patcher/patch_db.h b/src/xenia/patcher/patch_db.h new file mode 100644 index 000000000..f707c9281 --- /dev/null +++ b/src/xenia/patcher/patch_db.h @@ -0,0 +1,132 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_PATCH_DB_H_ +#define XENIA_PATCH_DB_H_ + +#include +#include +#include +#include + +#include "third_party/cpptoml/include/cpptoml.h" + +namespace xe { +namespace patcher { + +struct PatchDataValue { + const size_t alloc_size; + std::vector patch_data; + + template + PatchDataValue(const size_t size, const T value) : alloc_size(size) { + patch_data.resize(alloc_size); + std::memcpy(patch_data.data(), &value, alloc_size); + }; + + PatchDataValue(const std::vector value) : alloc_size(value.size()) { + patch_data.resize(alloc_size); + std::memcpy(patch_data.data(), value.data(), alloc_size); + }; + + PatchDataValue(const std::string value) : alloc_size(value.size()) { + patch_data.resize(alloc_size); + std::memcpy(patch_data.data(), value.c_str(), alloc_size); + }; + + PatchDataValue(const std::u16string value) : alloc_size(value.size() * 2) { + patch_data.resize(alloc_size); + std::memcpy(patch_data.data(), value.c_str(), alloc_size); + }; +}; + +struct PatchDataEntry { + const uint32_t address; + const PatchDataValue data; + + PatchDataEntry(const uint32_t memory_address, const PatchDataValue patch_data) + : address(memory_address), data(patch_data){}; +}; + +struct PatchInfoEntry { + uint32_t id; + std::string patch_name; + std::string patch_desc; + std::string patch_author; + std::vector patch_data; + bool is_enabled; +}; + +struct PatchFileEntry { + uint32_t title_id; + std::string title_name; + std::vector hashes; + std::vector patch_info; +}; + +enum class PatchDataType { + kBE8, + kBE16, + kBE32, + kBE64, + kF32, + kF64, + kString, + kU16String, + kByteArray +}; + +struct PatchData { + uint8_t size; + PatchDataType type; + + PatchData(uint8_t size_, PatchDataType type_) : size(size_), type(type_){}; +}; + +class PatchDB { + public: + PatchDB(const std::filesystem::path patches_root); + ~PatchDB(); + + void LoadPatches(); + + PatchFileEntry ReadPatchFile(const std::filesystem::path& file_path); + bool ReadPatchData(std::vector& patch_data, + const std::pair data_type, + const std::shared_ptr& patch_table); + + std::vector GetTitlePatches( + const uint32_t title_id, const std::optional hash); + std::vector& GetAllPatches() { return loaded_patches_; } + + private: + void ReadHashes(PatchFileEntry& patch_entry, + std::shared_ptr patch_toml_fields); + + inline static const std::regex patch_filename_regex_ = + std::regex("^[A-Fa-f0-9]{8}.*\\.patch\\.toml$"); + + const std::map patch_data_types_size_ = { + {"string", PatchData(0, PatchDataType::kString)}, + {"u16string", PatchData(0, PatchDataType::kU16String)}, + {"array", PatchData(0, PatchDataType::kByteArray)}, + {"f64", PatchData(sizeof(uint64_t), PatchDataType::kF64)}, + {"f32", PatchData(sizeof(uint32_t), PatchDataType::kF32)}, + {"be64", PatchData(sizeof(uint64_t), PatchDataType::kBE64)}, + {"be32", PatchData(sizeof(uint32_t), PatchDataType::kBE32)}, + {"be16", PatchData(sizeof(uint16_t), PatchDataType::kBE16)}, + {"be8", PatchData(sizeof(uint8_t), PatchDataType::kBE8)}}; + + std::vector loaded_patches_; + std::filesystem::path patches_root_; +}; +} // namespace patcher +} // namespace xe + +#endif // XENIA_PATCH_LOADER_H_ diff --git a/src/xenia/patcher/patcher.cc b/src/xenia/patcher/patcher.cc new file mode 100644 index 000000000..42ce42518 --- /dev/null +++ b/src/xenia/patcher/patcher.cc @@ -0,0 +1,66 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ +#include + +#include "xenia/base/logging.h" +#include "xenia/patcher/patcher.h" + +namespace xe { +namespace patcher { + +Patcher::Patcher(const std::filesystem::path patches_root) { + is_any_patch_applied_ = false; + patch_db_ = new PatchDB(patches_root); +} + +void Patcher::ApplyPatchesForTitle(Memory* memory, const uint32_t title_id, + const std::optional hash) { + const auto title_patches = patch_db_->GetTitlePatches(title_id, hash); + + for (const PatchFileEntry& patchFile : title_patches) { + for (const PatchInfoEntry& patchEntry : patchFile.patch_info) { + if (!patchEntry.is_enabled) { + continue; + } + XELOGE("Patcher: Applying patch for: {}({:08X}) - {}", + patchFile.title_name, patchFile.title_id, patchEntry.patch_name); + ApplyPatch(memory, &patchEntry); + } + } +} + +void Patcher::ApplyPatch(Memory* memory, const PatchInfoEntry* patch) { + for (const PatchDataEntry& patch_data_entry : patch->patch_data) { + uint32_t old_address_protect = 0; + uint8_t* address = memory->TranslateVirtual(patch_data_entry.address); + xe::BaseHeap* heap = memory->LookupHeap(patch_data_entry.address); + if (!heap) { + continue; + } + + heap->QueryProtect(patch_data_entry.address, &old_address_protect); + + heap->Protect(patch_data_entry.address, + (uint32_t)patch_data_entry.data.alloc_size, + kMemoryProtectRead | kMemoryProtectWrite); + + std::memcpy(address, patch_data_entry.data.patch_data.data(), + patch_data_entry.data.alloc_size); + + // Restore previous protection + heap->Protect(patch_data_entry.address, + (uint32_t)patch_data_entry.data.alloc_size, + old_address_protect); + + is_any_patch_applied_ = true; + } +} + +} // namespace patcher +} // namespace xe diff --git a/src/xenia/patcher/patcher.h b/src/xenia/patcher/patcher.h new file mode 100644 index 000000000..b32a9ba1d --- /dev/null +++ b/src/xenia/patcher/patcher.h @@ -0,0 +1,36 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_PATCHER_H_ +#define XENIA_PATCHER_H_ + +#include "xenia/memory.h" +#include "xenia/patcher/patch_db.h" + +namespace xe { +namespace patcher { + +class Patcher { + public: + Patcher(const std::filesystem::path patches_root); + + void ApplyPatch(Memory* memory, const PatchInfoEntry* patch); + void ApplyPatchesForTitle(Memory* memory, const uint32_t title_id, + const std::optional hash); + + bool IsAnyPatchApplied() { return is_any_patch_applied_; } + + private: + PatchDB* patch_db_; + bool is_any_patch_applied_; +}; + +} // namespace patcher +} // namespace xe +#endif