From f98e9ef33aa8ee2cc0960d0a5e8d55de3d63a95b Mon Sep 17 00:00:00 2001 From: Kuriko Moe Date: Mon, 2 Dec 2024 11:48:47 +0800 Subject: [PATCH] initial commit --- .clang-format | 213 +++++++++++++++++++++++++++++++++++++ .clang-tidy | 91 ++++++++++++++++ .gitignore | 87 +++++++++++++++ CMakeLists.txt | 41 +++++++ CMakePresets.json | 15 +++ README.md | 43 ++++++++ cmake/AutoCCache.cmake | 10 ++ cmake/presets/base.json | 15 +++ cmake/presets/linux.json | 77 ++++++++++++++ cmake/presets/windows.json | 76 +++++++++++++ include/.gitkeep | 0 justfile | 23 ++++ scripts/init_linux.sh | 8 ++ scripts/init_windows.nu | 10 ++ src/.gitkeep | 0 src/dllmain.cpp | 132 +++++++++++++++++++++++ src/helper.hpp | 107 +++++++++++++++++++ src/stdafx.h | 12 +++ test/.gitkeep | 0 vcpkg-configuration.json | 14 +++ vcpkg.json | 6 ++ 21 files changed, 980 insertions(+) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CMakePresets.json create mode 100644 README.md create mode 100644 cmake/AutoCCache.cmake create mode 100644 cmake/presets/base.json create mode 100644 cmake/presets/linux.json create mode 100644 cmake/presets/windows.json create mode 100644 include/.gitkeep create mode 100644 justfile create mode 100644 scripts/init_linux.sh create mode 100644 scripts/init_windows.nu create mode 100644 src/.gitkeep create mode 100644 src/dllmain.cpp create mode 100644 src/helper.hpp create mode 100644 src/stdafx.h create mode 100644 test/.gitkeep create mode 100644 vcpkg-configuration.json create mode 100644 vcpkg.json diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..18850fe --- /dev/null +++ b/.clang-format @@ -0,0 +1,213 @@ +# 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto +Language: Cpp +# BasedOnStyle: Google + +# 访问说明符(public、private等)的偏移 +AccessModifierOffset: -4 + +# 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行) +AlignAfterOpenBracket: Align + +# 连续赋值时,对齐所有等号 +AlignConsecutiveAssignments: false + +# 连续声明时,对齐所有声明的变量名 +AlignConsecutiveDeclarations: false + +# 右对齐逃脱换行(使用反斜杠换行)的反斜杠 +AlignEscapedNewlines: Right + +# 水平对齐二元和三元表达式的操作数 +AlignOperands: true + +# 对齐连续的尾随的注释 +AlignTrailingComments: true + +# 不允许函数声明的所有参数在放在下一行 +AllowAllParametersOfDeclarationOnNextLine: false + +# 不允许短的块放在同一行 +AllowShortBlocksOnASingleLine: true + +# 允许短的case标签放在同一行 +AllowShortCaseLabelsOnASingleLine: true + +# 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All +AllowShortFunctionsOnASingleLine: None + +# 允许短的if语句保持在同一行 +AllowShortIfStatementsOnASingleLine: true + +# 允许短的循环保持在同一行 +AllowShortLoopsOnASingleLine: true + +# 总是在返回类型后换行: None, All, TopLevel(顶级函数,不包括在类中的函数), +# AllDefinitions(所有的定义,不包括声明), TopLevelDefinitions(所有的顶级函数的定义) +AlwaysBreakAfterReturnType: None + +# 总是在多行string字面量前换行 +AlwaysBreakBeforeMultilineStrings: false + +# 总是在template声明后换行 +AlwaysBreakTemplateDeclarations: true + +# false表示函数实参要么都在同一行,要么都各自一行 +BinPackArguments: true + +# false表示所有形参要么都在同一行,要么都各自一行 +BinPackParameters: true + +# 大括号换行,只有当BreakBeforeBraces设置为Custom时才有效 +BraceWrapping: + # class定义后面 + AfterClass: false + # 控制语句后面 + AfterControlStatement: false + # enum定义后面 + AfterEnum: false + # 函数定义后面 + AfterFunction: false + # 命名空间定义后面 + AfterNamespace: false + # struct定义后面 + AfterStruct: false + # union定义后面 + AfterUnion: false + # extern之后 + AfterExternBlock: false + # catch之前 + BeforeCatch: false + # else之前 + BeforeElse: false + # 缩进大括号 + IndentBraces: false + # 分离空函数 + SplitEmptyFunction: false + # 分离空语句 + SplitEmptyRecord: false + # 分离空命名空间 + SplitEmptyNamespace: false + +# 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行) +BreakBeforeBinaryOperators: NonAssignment + +# 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义,与Attach类似), +# Mozilla(除枚举、函数、记录定义,与Attach类似), Stroustrup(除函数定义、catch、else,与Attach类似), +# Allman(总是在大括号前换行), GNU(总是在大括号前换行,并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom +# 注:这里认为语句块也属于函数 +BreakBeforeBraces: Custom + +# 在三元运算符前换行 +BreakBeforeTernaryOperators: false + +# 在构造函数的初始化列表的冒号后换行 +BreakConstructorInitializers: AfterColon + +#BreakInheritanceList: AfterColon + +BreakStringLiterals: false + +# 每行字符的限制,0表示没有限制 +ColumnLimit: 0 + +CompactNamespaces: true + +# 构造函数的初始化列表要么都在同一行,要么都各自一行 +ConstructorInitializerAllOnOneLineOrOnePerLine: false + +# 构造函数的初始化列表的缩进宽度 +ConstructorInitializerIndentWidth: 4 + +# 延续的行的缩进宽度 +ContinuationIndentWidth: 4 + +# 去除C++11的列表初始化的大括号{后和}前的空格 +Cpp11BracedListStyle: true + +# 继承最常用的指针和引用的对齐方式 +DerivePointerAlignment: false + +# 固定命名空间注释 +FixNamespaceComments: true + +# 缩进case标签 +IndentCaseLabels: false + +IndentPPDirectives: None + +# 缩进宽度 +IndentWidth: 4 + +# 函数返回类型换行时,缩进函数声明或函数定义的函数名 +IndentWrappedFunctionNames: false + +# 保留在块开始处的空行 +KeepEmptyLinesAtTheStartOfBlocks: false + +# 连续空行的最大数量 +MaxEmptyLinesToKeep: 1 + +# 命名空间的缩进: None, Inner(缩进嵌套的命名空间中的内容), All +NamespaceIndentation: None + +# 指针和引用的对齐: Left, Right, Middle +PointerAlignment: Right + +# 允许重新排版注释 +ReflowComments: true + +# 允许排序#include +SortIncludes: false + +# 允许排序 using 声明 +SortUsingDeclarations: false + +# 在C风格类型转换后添加空格 +SpaceAfterCStyleCast: false + +# 在Template 关键字后面添加空格 +SpaceAfterTemplateKeyword: true + +# 在赋值运算符之前添加空格 +SpaceBeforeAssignmentOperators: true + +# SpaceBeforeCpp11BracedList: true + +# SpaceBeforeCtorInitializerColon: true + +# SpaceBeforeInheritanceColon: true + +# 开圆括号之前添加一个空格: Never, ControlStatements, Always +SpaceBeforeParens: ControlStatements + +# SpaceBeforeRangeBasedForLoopColon: true + +# 在空的圆括号中添加空格 +SpaceInEmptyParentheses: false + +# 在尾随的评论前添加的空格数(只适用于//) +SpacesBeforeTrailingComments: 1 + +# 在尖括号的<后和>前添加空格 +SpacesInAngles: false + +# 在C风格类型转换的括号中添加空格 +SpacesInCStyleCastParentheses: false + +# 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格 +SpacesInContainerLiterals: true + +# 在圆括号的(后和)前添加空格 +SpacesInParentheses: false + +# 在方括号的[后和]前添加空格,lamda表达式和未指明大小的数组的声明不受影响 +SpacesInSquareBrackets: false + +# 标准: Cpp03, Cpp11, Auto +Standard: Cpp11 + +# tab宽度 +TabWidth: 4 + +# 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always +UseTab: Never diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..be3e581 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,91 @@ +--- +# Configure clang-tidy for this project. + +# Here is an explanation for why some of the checks are disabled: +# +# -google-readability-namespace-comments: the *_CLIENT_NS is a macro, and +# clang-tidy fails to match it against the initial value. +# +# -modernize-use-trailing-return-type: clang-tidy recommends using +# `auto Foo() -> std::string { return ...; }`, we think the code is less +# readable in this form. +# +# -modernize-return-braced-init-list: We think removing typenames and using +# only braced-init can hurt readability. +# +# -performance-move-const-arg: This warning requires the developer to +# know/care more about the implementation details of types/functions than +# should be necessary. For example, `A a; F(std::move(a));` will trigger a +# warning IFF `A` is a trivial type (and therefore the move is +# meaningless). It would also warn if `F` accepts by `const&`, which is +# another detail that the caller need not care about. +# +# -readability-redundant-declaration: A friend declaration inside a class +# counts as a declaration, so if we also declare that friend outside the +# class in order to document it as part of the public API, that will +# trigger a redundant declaration warning from this check. +# +Checks: > + -*, + bugprone-*, + google-*, + misc-*, + modernize-*, + performance-*, + portability-*, + readability-*, + -google-readability-braces-around-statements, + -google-readability-namespace-comments, + -google-runtime-references, + -misc-non-private-member-variables-in-classes, + -modernize-return-braced-init-list, + -performance-move-const-arg, + -readability-named-parameter, + -modernize-use-trailing-return-type, + -readability-braces-around-statements, + -readability-redundant-declaration, + -readability-magic-numbers, + -readability-identifier-length, + -modernize-use-nodiscard, + -bugprone-easily-swappable-parameters, + -modernize-avoid-c-arrays, + -readability-redundant-control-flow, + -google-build-using-namespace, + +# -modernize-use-nodiscard: [[nodiscard]] for const func +# -bugprone-easily-swappable-parameters: I cannot avoid this fn(i32, i32) +# -modernize-avoid-c-arrays: CArray is sometimes useful for reverse engineering +# -readability-redundant-control-flow: What can i say +# -google-build-using-namespace: I know, but sometimes I need this. + +# Turn all the warnings from the checks above into errors. +# WarningsAsErrors: "*" + +# TODO(#205) - Enable clang-tidy checks in our headers. +# HeaderFilterRegex: "google/cloud/.*" + +CheckOptions: + # - { key: readability-identifier-naming.NamespaceCase, value: lower_case } + # - { key: readability-identifier-naming.ClassCase, value: CamelCase } + # - { key: readability-identifier-naming.StructCase, value: CamelCase } + # - { key: readability-identifier-naming.TemplateParameterCase, value: CamelCase } + # - { key: readability-identifier-naming.FunctionCase, value: aNy_CasE } + # - { key: readability-identifier-naming.VariableCase, value: lower_case } + # - { key: readability-identifier-naming.ClassMemberCase, value: lower_case } + # - { key: readability-identifier-naming.ClassMemberSuffix, value: _ } + # - { key: readability-identifier-naming.PrivateMemberSuffix, value: _ } + # - { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ } + # - { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE } + # - { key: readability-identifier-naming.EnumConstantCase, value: CamelCase } + # - { key: readability-identifier-naming.EnumConstantPrefix, value: k } + # - { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase } + # - { key: readability-identifier-naming.ConstexprVariablePrefix, value: k } + # - { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase } + # - { key: readability-identifier-naming.GlobalConstantPrefix, value: k } + # - { key: readability-identifier-naming.MemberConstantCase, value: CamelCase } + # - { key: readability-identifier-naming.MemberConstantPrefix, value: k } + # - { key: readability-identifier-naming.StaticConstantCase, value: CamelCase } + # - { key: readability-identifier-naming.StaticConstantPrefix, value: k } + # - { key: readability-implicit-bool-conversion.AllowIntegerConditions, value: 1 } + # - { key: readability-implicit-bool-conversion.AllowPointerConditions, value: 1 } + - { key: readability-identifier-naming.VariableHungarianPrefix, value: ON } diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96903d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,87 @@ +/release +/build + +.vscode/ +.idea/ + +# cmake build dir +.build/ +# cmake cache dir +.cache/ +# cmake binary output dir +.output/ + +# vcpkg dir +vcpkg/ + +# Created by https://www.toptal.com/developers/gitignore/api/vcpkg +# Edit at https://www.toptal.com/developers/gitignore?templates=vcpkg + +### vcpkg ### +# Vcpkg + +vcpkg-manifest-install.log + +## Manifest Mode +vcpkg_installed/ + +# End of https://www.toptal.com/developers/gitignore/api/vcpkg +# Created by https://www.toptal.com/developers/gitignore/api/cmake +# Edit at https://www.toptal.com/developers/gitignore?templates=cmake + +### CMake ### +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +### CMake Patch ### +# External projects +*-prefix/ + +# End of https://www.toptal.com/developers/gitignore/api/cmake +# Created by https://www.toptal.com/developers/gitignore/api/c++ +# Edit at https://www.toptal.com/developers/gitignore?templates=c++ + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# End of https://www.toptal.com/developers/gitignore/api/c++ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3065b1d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.25.0) + +set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake CACHE STRING "") +set(CMAKE_EXPORT_COMPILE_COMMANDS true) + +# set(CMAKE_GENERATOR_TOOLSET "v140" CACHE STRING "Platform Toolset" FORCE) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +include(AutoCCache) + +project(KProject) + +set(CMAKE_CXX_STANDARD 20) +# set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") +# set(CMAKE_CXX_FLAGS_RELEASE "/MT") +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +# find_package(Catch2 CONFIG REQUIRED) +# target_link_libraries(main PRIVATE Catch2::Catch2 Catch2::Catch2WithMain) + +find_package(minhook CONFIG REQUIRED) + +find_package(spdlog CONFIG REQUIRED) + +set(TARGET "kpatch") +set(TARGET_SRC src/dllmain.cpp) +set(TARGET_OUTPUT_NAME "kpatch") +add_library(${TARGET} SHARED ${TARGET_SRC}) +set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME ${TARGET_OUTPUT_NAME}) +target_include_directories(${TARGET} PRIVATE ${CMAKE_SOURCE_DIR}/include) +target_link_libraries(${TARGET} PUBLIC spdlog::spdlog) +# target_link_libraries(${TARGET} PUBLIC minhook::minhook) + + +# ------------ post build ------------ +# copy compile_commands.json for clangd +if(EXISTS "${CMAKE_BINARY_DIR}/compile_commands.json") + file(COPY "${CMAKE_BINARY_DIR}/compile_commands.json" DESTINATION "${CMAKE_SOURCE_DIR}") +endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..72e87f3 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,15 @@ +{ + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 25, + "patch": 0 + }, + "include": [ + "cmake/presets/linux.json", + "cmake/presets/windows.json" + ], + "configurePresets": [ ], + "buildPresets": [ ], + "testPresets": [ ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..cee4398 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +### L4D2 Too Many Indices Fix + +用来处理这个问题: + +![26cc8e80ba807d378291266749f824bc](https://github.com/user-attachments/assets/9a6aed1c-ef76-43a0-b9bb-dd357f0b9261) + +### 编译 +``` +just config build +``` + +### 原理解析: + +> 虽然一开始是靠逆向找的,后来发现自己犯傻了,明明 source 引擎是开源了部分内容的,直接看源代码不香么 QAQ + +逆向的话,基本上就是定位字符串,之后 trace 追一下代码的执行流(或者直接搜索立即数 0x8000,反正也没几个结果,之后猜一下函数是干什么的) + + + +从源代码更简单,直接看就行了。 + +```c++ +enum +{ + VERTEX_BUFFER_SIZE = 32768, // 罪魁祸首,你开这么小的 buffer 干什么 + MAX_QUAD_INDICES = 16384, +}; +``` + +位置: https://github.com/nillerusr/source-engine/blob/29985681a18508e78dc79ad863952f830be237b6/materialsystem/shaderapidx9/meshdx8.cpp#L71 + + + +对比 dx10:(32倍差距) + +```c++ +VERTEX_BUFFER_SIZE = 1024 * 1024 +``` + + +### 修正方法 + +一开始是直接 patch 立即数,但是 L4D2 更新有点频繁,就索性直接写成动态 patch 了。 diff --git a/cmake/AutoCCache.cmake b/cmake/AutoCCache.cmake new file mode 100644 index 0000000..734952b --- /dev/null +++ b/cmake/AutoCCache.cmake @@ -0,0 +1,10 @@ +# From https://gist.github.com/cgmb/c48bfa77da6c825a27bf05d25001d321 + +option(AUTO_CCACHE "Use ccache to speed up rebuilds" ON) +find_program(CCACHE_PROGRAM ccache) +if(CCACHE_PROGRAM AND ${AUTO_CCACHE}) + message(STATUS "Using ${CCACHE_PROGRAM} as compiler launcher") + set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") + # requires at least CMake 3.9 to be any use + set(CMAKE_CUDA_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") +endif() diff --git a/cmake/presets/base.json b/cmake/presets/base.json new file mode 100644 index 0000000..0b84261 --- /dev/null +++ b/cmake/presets/base.json @@ -0,0 +1,15 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "config-base", + "displayName": "base Configuration", + "hidden": true, + "binaryDir": "${sourceDir}/build/${presetName}", + "installDir": "${sourceDir}/install/${presetName}", + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": true + } + } + ] +} diff --git a/cmake/presets/linux.json b/cmake/presets/linux.json new file mode 100644 index 0000000..404d297 --- /dev/null +++ b/cmake/presets/linux.json @@ -0,0 +1,77 @@ +{ + "version": 6, + "include": [ "base.json" ], + "configurePresets": [ + { + "name": "config-base-linux", + "displayName": "base Configuration for linux", + "inherits": "config-base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "x64-debug", + "inherits": "config-base-linux", + "generator": "Ninja", + "architecture":{ + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE" : "Debug" + } + }, + { + "name": "x64-release", + "inherits": "x64-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE" : "Release" + } + }, + { + "name": "x86-debug", + "inherits": "config-base-linux", + "generator": "Ninja", + "architecture":{ + "value": "x86", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE" : "Debug", + "CMAKE_C_FLAGS": "-m32", + "CMAKE_CXX_FLAGS": "-m32", + "CMAKE_SYSTEM_PROCESSOR": "x86" + } + }, + { + "name": "x86-release", + "inherits": "x86-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE" : "Release" + } + } + ], + "buildPresets": [ + { + "name": "x64-debug", + "configurePreset": "x64-debug" + }, + { + "name": "x64-release", + "configurePreset": "x64-release" + }, + { + "name": "x86-debug", + "configurePreset": "x86-debug" + }, + { + "name": "x86-release", + "configurePreset": "x86-release" + } + ], + "testPresets": [ ] +} diff --git a/cmake/presets/windows.json b/cmake/presets/windows.json new file mode 100644 index 0000000..e1e8a20 --- /dev/null +++ b/cmake/presets/windows.json @@ -0,0 +1,76 @@ +{ + "version": 6, + "include": ["base.json"], + "configurePresets": [ + { + "name": "config-base-windows", + "displayName": "base Configuration for windows", + "inherits": "config-base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "x64-debug-msvc", + "inherits": "config-base-windows", + "generator": "", + "architecture":{ + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE" : "Debug", + "CMAKE_GENERATOR_PLATFORM": "x64" + } + }, + { + "name": "x64-release-msvc", + "inherits": "x64-debug-msvc", + "cacheVariables": { + "CMAKE_BUILD_TYPE" : "Release" + } + }, + { + "name": "x86-debug-msvc", + "inherits": "config-base-windows", + "generator": "", + "architecture":{ + "value": "x86", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE" : "Debug", + "CMAKE_GENERATOR_PLATFORM": "Win32" + } + }, + { + "name": "x86-release-msvc", + "inherits": "x86-debug-msvc", + "cacheVariables": { + "CMAKE_BUILD_TYPE" : "Release" + } + } + ], + "buildPresets": [ + { + "name": "x64-debug-msvc", + "configurePreset": "x64-debug-msvc" + }, + { + "name": "x64-release-msvc", + "configurePreset": "x64-release-msvc" + }, + { + "name": "x86-debug-msvc", + "configurePreset": "x86-debug-msvc" + }, + { + "name": "x86-release-msvc", + "configurePreset": "x86-release-msvc" + } + ], + "testPresets": [ ] +} diff --git a/include/.gitkeep b/include/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/justfile b/justfile new file mode 100644 index 0000000..e1ba2f4 --- /dev/null +++ b/justfile @@ -0,0 +1,23 @@ +TEMPLATE_VER := "v1.0.0" +TARGET := "F:/Games/SteamLibrary/steamapps/common/Left 4 Dead 2" + +# use `just preset=x64-release config build` to override this +preset := "x86-release-msvc" +target := "main" + +[linux] +init: + bash ./scripts/init_linux.sh + +clean: + rm -rf build + +config: + cmake --preset {{preset}} + +build: + cmake --build --preset {{preset}} --config Release + cp -f ./build/x86-release-msvc/bin/Release/kpatch.dll "{{TARGET}}/kpatch.asi" + +@run: + ./build/{{preset}}/bin/{{target}} diff --git a/scripts/init_linux.sh b/scripts/init_linux.sh new file mode 100644 index 0000000..242d54b --- /dev/null +++ b/scripts/init_linux.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -xe + +git clone https://github.com/microsoft/vcpkg.git --depth 1 +./vcpkg/bootstrap-vcpkg.sh + +./vcpkg/vcpkg new --application diff --git a/scripts/init_windows.nu b/scripts/init_windows.nu new file mode 100644 index 0000000..6e5ab53 --- /dev/null +++ b/scripts/init_windows.nu @@ -0,0 +1,10 @@ +#!nu + +echo "init vcpkg" +git clone https://github.com/microsoft/vcpkg.git --depth 1 +./vcpkg/bootstrap-vcpkg.bat + +./vcpkg/vcpkg new --application + +echo "Retarget from linux preset `x64-debug` to windows preset `x64-debug-msvc`" +open justfile | str replace r#'"x64-debug"'# r#'"x64-debug-msvc"'# | save justfile -f diff --git a/src/.gitkeep b/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/dllmain.cpp b/src/dllmain.cpp new file mode 100644 index 0000000..6cac781 --- /dev/null +++ b/src/dllmain.cpp @@ -0,0 +1,132 @@ +#include "stdafx.h" +#include "helper.hpp" +#include +#include +#include + +HMODULE baseModule = GetModuleHandle(NULL); +HMODULE thisModule; // Fix DLL + +// Version +std::string sFixName = "L4D2Fix"; +std::string sFixVer = "0.0.1"; +std::string sLogFile = sFixName + ".log"; + +// Logger +std::shared_ptr logger; +std::filesystem::path sExePath; +std::string sExeName; +std::filesystem::path sThisModulePath; + +void Logging() +{ + // Get this module path + WCHAR thisModulePath[_MAX_PATH] = { 0 }; + GetModuleFileNameW(thisModule, thisModulePath, MAX_PATH); + sThisModulePath = thisModulePath; + sThisModulePath = sThisModulePath.remove_filename(); + + // Get game name and exe path + WCHAR exePath[_MAX_PATH] = { 0 }; + GetModuleFileNameW(baseModule, exePath, MAX_PATH); + sExePath = exePath; + sExeName = sExePath.filename().string(); + sExePath = sExePath.remove_filename(); + + // spdlog initialisation + { + try { + // Create 10MB truncated logger + auto logger = spdlog::rotating_logger_mt(sFixName, sLogFile, 1048576 * 5, 3); + spdlog::set_default_logger(logger); + + spdlog::flush_on(spdlog::level::debug); + spdlog::info("----------"); + spdlog::info("{} v{} loaded.", sFixName.c_str(), sFixVer.c_str()); + spdlog::info("----------"); + spdlog::info("Log file: {}", sThisModulePath.string() + sLogFile); + spdlog::info("----------"); + + // Log module details + spdlog::info("Module Name: {0:s}", sExeName.c_str()); + spdlog::info("Module Path: {0:s}", sExePath.string()); + spdlog::info("Module Address: 0x{0:x}", (uintptr_t)baseModule); + spdlog::info("Module Timestamp: {0:d}", Memory::ModuleTimestamp(baseModule)); + spdlog::info("----------"); + } + catch (const spdlog::spdlog_ex& ex) { + AllocConsole(); + FILE* dummy; + freopen_s(&dummy, "CONOUT$", "w", stdout); + std::cout << "Log initialisation failed: " << ex.what() << std::endl; + FreeLibraryAndExitThread(baseModule, 1); + } + } +} + +DWORD __stdcall Main(void*) { + Logging(); + std::filesystem::path startup_check = L"success.txt"; + if (!std::filesystem::exists(startup_check)) { + MessageBoxW(NULL, L"这是一个启动测试,用于检验补丁是否正常运行。\n请注意由于修改内存,请不要进 VAC 服,后果未知。\n本弹窗仅首次启动出现,后续运行情况参见 L4D2Fix.log 日志文件。\n\n关注B站 5050 直播间,谢谢喵 by KurikoMoe!", L"L4D2 Fix", MB_OK); + } + + auto hDll = LoadLibraryW(L"shaderapidx9.dll"); + + if (hDll == nullptr) { + MessageBoxW(NULL, L"Failed to load shaderapidx9.dll!", L"L4D2 Fix", MB_OK); + } + + auto f = [&](const char* tag, const char* pattern, size_t offset, size_t w_offset) { + std::uint8_t* ptr = Memory::PatternScan(hDll, pattern); + + if (ptr != nullptr) { + Memory::Write((uintptr_t)(ptr + w_offset), (uint32_t)0x020000); + spdlog::info("Modified the vertex max buffer shaderapidx9.dll: {}", tag); + return ptr; + } + + spdlog::error("Pattern scan failed for shaderapidx9.dll: {}", tag); + return (uint8_t*)nullptr; + }; + + // CreateEmptyColorMesh + auto ptr1 = f("CreateEmptyColorMesh", "68 ?? ?? ?? ?? 68 00 80 00 00 6A 04", 0, 6); + + auto ptr1_1 = f("CreateEmptyColorMesh Later", "68 00 80 00 00 50 8B", (size_t)ptr1, 1); + + // CreateVertexIDBuffer + auto ptr2 = f("CreateVertexIDBuffer", "68 ?? ?? ?? ?? 68 00 80 00 00 6A 04", (size_t)ptr1_1, 6); + auto ptr2_1 = f("CreateVertexIDBuffer Later", "68 00 80 00 00 50 8B", (size_t)ptr2, 1); + + // Not known + auto ptr3 = f("Unknown", "0F B6 C0 50 68 00 80 00 00 53 8B CF E8 ?? ?? ?? ??", (size_t)ptr1, 5); + + if (ptr1 == nullptr || ptr2 == nullptr || ptr3 == nullptr) { + MessageBoxW(NULL, L"未能正常应用补丁,patch 未生效,请联系开发者", L"L4D2 Fix", MB_OK); + } else { + std::fstream file(startup_check, std::ios::out); + file << "补丁已生效,关闭启动提示,删除本文件可以重新启用" << std::endl; + } + + return true; +} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { + thisModule = hModule; + + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: + HANDLE mainHandle = CreateThread(NULL, 0, Main, 0, CREATE_SUSPENDED, 0); + if (mainHandle) { + SetThreadPriority(mainHandle, THREAD_PRIORITY_TIME_CRITICAL); + ResumeThread(mainHandle); + CloseHandle(mainHandle); + } + break; + } + return TRUE; +} + + +void __declspec(dllexport) kuriko_export() {} diff --git a/src/helper.hpp b/src/helper.hpp new file mode 100644 index 0000000..bd6a217 --- /dev/null +++ b/src/helper.hpp @@ -0,0 +1,107 @@ +#include "stdafx.h" + +namespace Memory +{ + template + void Write(uintptr_t writeAddress, T value) + { + DWORD oldProtect; + VirtualProtect((LPVOID)(writeAddress), sizeof(T), PAGE_EXECUTE_WRITECOPY, &oldProtect); + *(reinterpret_cast(writeAddress)) = value; + VirtualProtect((LPVOID)(writeAddress), sizeof(T), oldProtect, &oldProtect); + } + + void PatchBytes(uintptr_t address, const char* pattern, unsigned int numBytes) + { + DWORD oldProtect; + VirtualProtect((LPVOID)address, numBytes, PAGE_EXECUTE_READWRITE, &oldProtect); + memcpy((LPVOID)address, pattern, numBytes); + VirtualProtect((LPVOID)address, numBytes, oldProtect, &oldProtect); + } + + // CSGOSimple's pattern scan + // https://github.com/OneshotGH/CSGOSimple-master/blob/master/CSGOSimple/helpers/utils.cpp + std::uint8_t* PatternScan(void* module, const char* signature, size_t offset=0) + { + static auto pattern_to_byte = [](const char* pattern) { + auto bytes = std::vector{}; + auto start = const_cast(pattern); + auto end = const_cast(pattern) + strlen(pattern); + + for (auto current = start; current < end; ++current) { + if (*current == '?') { + ++current; + if (*current == '?') + ++current; + bytes.push_back(-1); + } + else { + bytes.push_back(strtoul(current, ¤t, 16)); + } + } + return bytes; + }; + + auto dosHeader = (PIMAGE_DOS_HEADER)module; + auto ntHeaders = (PIMAGE_NT_HEADERS)((std::uint8_t*)module + dosHeader->e_lfanew); + + auto sizeOfImage = ntHeaders->OptionalHeader.SizeOfImage; + auto patternBytes = pattern_to_byte(signature); + auto scanBytes = reinterpret_cast(module); + + auto s = patternBytes.size(); + auto d = patternBytes.data(); + + for (auto i = offset; i < sizeOfImage - s; ++i) { + bool found = true; + for (auto j = 0ul; j < s; ++j) { + if (scanBytes[i + j] != d[j] && d[j] != -1) { + found = false; + break; + } + } + if (found) { + return &scanBytes[i]; + } + } + return nullptr; + } + + static HMODULE GetThisDllHandle() + { + MEMORY_BASIC_INFORMATION info; + size_t len = VirtualQueryEx(GetCurrentProcess(), (void*)GetThisDllHandle, &info, sizeof(info)); + assert(len == sizeof(info)); + return len ? (HMODULE)info.AllocationBase : NULL; + } + + uint32_t ModuleTimestamp(void* module) + { + auto dosHeader = (PIMAGE_DOS_HEADER)module; + auto ntHeaders = (PIMAGE_NT_HEADERS)((std::uint8_t*)module + dosHeader->e_lfanew); + return ntHeaders->FileHeader.TimeDateStamp; + } + + uintptr_t GetAbsolute(uintptr_t address) noexcept + { + return (address + 4 + *reinterpret_cast(address)); + } +} + +// namespace Util +// { +// std::pair GetPhysicalDesktopDimensions() { +// if (DEVMODE devMode{ .dmSize = sizeof(DEVMODE) }; EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &devMode)) +// return { devMode.dmPelsWidth, devMode.dmPelsHeight }; + +// return {}; +// } + +// int HexStringToInt(const std::string& hexString) { +// int num; +// std::stringstream ss; +// ss << std::hex << hexString; +// ss >> num; +// return num; +// } +// } diff --git a/src/stdafx.h b/src/stdafx.h new file mode 100644 index 0000000..8fc1c1b --- /dev/null +++ b/src/stdafx.h @@ -0,0 +1,12 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/test/.gitkeep b/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 0000000..b299a10 --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1,14 @@ +{ + "default-registry": { + "kind": "git", + "baseline": "f523208df4ae467436dc2d2eeab594fed240d552", + "repository": "https://github.com/microsoft/vcpkg" + }, + "registries": [ + { + "kind": "artifact", + "location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip", + "name": "microsoft" + } + ] +} diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..fe2b23d --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,6 @@ +{ + "dependencies": [ + "spdlog", + "minhook" + ] +}