├── .clang-format ├── .github └── workflows │ ├── build.yml │ └── update-submodules.yml ├── .gitignore ├── .gitmodules ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── modules.xml ├── vcs.xml └── vivaldi_plus.iml ├── .vscode ├── c_cpp_properties.json └── settings.json ├── LICENSE ├── README.md ├── VC-LTL5.lua ├── src ├── PakFile.h ├── PakPatch.h ├── TabBookmark.h ├── appid.h ├── fastsearch.h ├── featuresFlag.h ├── green.h ├── hijack.h ├── patch.h ├── portable.h ├── utils.h ├── version.h ├── vivaldi++.cpp └── vivaldi++.rc ├── sweep.yaml └── xmake.lua /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Microsoft 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignArrayOfStructures: None 7 | AlignConsecutiveMacros: None 8 | AlignConsecutiveAssignments: None 9 | AlignConsecutiveBitFields: None 10 | AlignConsecutiveDeclarations: None 11 | AlignEscapedNewlines: Right 12 | AlignOperands: Align 13 | AlignTrailingComments: true 14 | AllowAllArgumentsOnNextLine: true 15 | AllowAllConstructorInitializersOnNextLine: true 16 | AllowAllParametersOfDeclarationOnNextLine: true 17 | AllowShortEnumsOnASingleLine: false 18 | AllowShortBlocksOnASingleLine: Never 19 | AllowShortCaseLabelsOnASingleLine: false 20 | AllowShortFunctionsOnASingleLine: None 21 | AllowShortLambdasOnASingleLine: All 22 | AllowShortIfStatementsOnASingleLine: Never 23 | AllowShortLoopsOnASingleLine: false 24 | AlwaysBreakAfterDefinitionReturnType: None 25 | AlwaysBreakAfterReturnType: None 26 | AlwaysBreakBeforeMultilineStrings: false 27 | AlwaysBreakTemplateDeclarations: MultiLine 28 | AttributeMacros: 29 | - __capability 30 | BinPackArguments: true 31 | BinPackParameters: true 32 | BraceWrapping: 33 | AfterCaseLabel: false 34 | AfterClass: true 35 | AfterControlStatement: Always 36 | AfterEnum: true 37 | AfterFunction: true 38 | AfterNamespace: true 39 | AfterObjCDeclaration: true 40 | AfterStruct: true 41 | AfterUnion: false 42 | AfterExternBlock: true 43 | BeforeCatch: true 44 | BeforeElse: true 45 | BeforeLambdaBody: false 46 | BeforeWhile: false 47 | IndentBraces: false 48 | SplitEmptyFunction: true 49 | SplitEmptyRecord: true 50 | SplitEmptyNamespace: true 51 | BreakBeforeBinaryOperators: None 52 | BreakBeforeConceptDeclarations: true 53 | BreakBeforeBraces: Custom 54 | BreakBeforeInheritanceComma: false 55 | BreakInheritanceList: BeforeColon 56 | BreakBeforeTernaryOperators: true 57 | BreakConstructorInitializersBeforeComma: false 58 | BreakConstructorInitializers: BeforeColon 59 | BreakAfterJavaFieldAnnotations: false 60 | BreakStringLiterals: true 61 | ColumnLimit: 0 62 | CommentPragmas: '^ IWYU pragma:' 63 | CompactNamespaces: false 64 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 65 | ConstructorInitializerIndentWidth: 4 66 | ContinuationIndentWidth: 4 67 | Cpp11BracedListStyle: true 68 | DeriveLineEnding: true 69 | DerivePointerAlignment: false 70 | DisableFormat: false 71 | EmptyLineAfterAccessModifier: Never 72 | EmptyLineBeforeAccessModifier: LogicalBlock 73 | ExperimentalAutoDetectBinPacking: false 74 | FixNamespaceComments: true 75 | ForEachMacros: 76 | - foreach 77 | - Q_FOREACH 78 | - BOOST_FOREACH 79 | IfMacros: 80 | - KJ_IF_MAYBE 81 | IncludeBlocks: Preserve 82 | IncludeCategories: 83 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 84 | Priority: 2 85 | SortPriority: 0 86 | CaseSensitive: false 87 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 88 | Priority: 3 89 | SortPriority: 0 90 | CaseSensitive: false 91 | - Regex: '.*' 92 | Priority: 1 93 | SortPriority: 0 94 | CaseSensitive: false 95 | IncludeIsMainRegex: '(Test)?$' 96 | IncludeIsMainSourceRegex: '' 97 | IndentAccessModifiers: false 98 | IndentCaseLabels: false 99 | IndentCaseBlocks: false 100 | IndentGotoLabels: true 101 | IndentPPDirectives: None 102 | IndentExternBlock: AfterExternBlock 103 | IndentRequires: false 104 | IndentWidth: 4 105 | IndentWrappedFunctionNames: false 106 | InsertTrailingCommas: None 107 | JavaScriptQuotes: Leave 108 | JavaScriptWrapImports: true 109 | KeepEmptyLinesAtTheStartOfBlocks: true 110 | LambdaBodyIndentation: Signature 111 | MacroBlockBegin: '' 112 | MacroBlockEnd: '' 113 | MaxEmptyLinesToKeep: 1 114 | NamespaceIndentation: None 115 | ObjCBinPackProtocolList: Auto 116 | ObjCBlockIndentWidth: 2 117 | ObjCBreakBeforeNestedBlockParam: true 118 | ObjCSpaceAfterProperty: false 119 | ObjCSpaceBeforeProtocolList: true 120 | PenaltyBreakAssignment: 2 121 | PenaltyBreakBeforeFirstCallParameter: 19 122 | PenaltyBreakComment: 300 123 | PenaltyBreakFirstLessLess: 120 124 | PenaltyBreakString: 1000 125 | PenaltyBreakTemplateDeclaration: 10 126 | PenaltyExcessCharacter: 1000000 127 | PenaltyReturnTypeOnItsOwnLine: 1000 128 | PenaltyIndentedWhitespace: 0 129 | PointerAlignment: Right 130 | PPIndentWidth: -1 131 | ReferenceAlignment: Pointer 132 | ReflowComments: true 133 | ShortNamespaceLines: 1 134 | SortIncludes: false 135 | SortJavaStaticImport: Before 136 | SortUsingDeclarations: true 137 | SpaceAfterCStyleCast: false 138 | SpaceAfterLogicalNot: false 139 | SpaceAfterTemplateKeyword: true 140 | SpaceBeforeAssignmentOperators: true 141 | SpaceBeforeCaseColon: false 142 | SpaceBeforeCpp11BracedList: false 143 | SpaceBeforeCtorInitializerColon: true 144 | SpaceBeforeInheritanceColon: true 145 | SpaceBeforeParens: ControlStatements 146 | SpaceAroundPointerQualifiers: Default 147 | SpaceBeforeRangeBasedForLoopColon: true 148 | SpaceInEmptyBlock: false 149 | SpaceInEmptyParentheses: false 150 | SpacesBeforeTrailingComments: 1 151 | SpacesInAngles: Never 152 | SpacesInConditionalStatement: false 153 | SpacesInContainerLiterals: true 154 | SpacesInCStyleCastParentheses: false 155 | SpacesInLineCommentPrefix: 156 | Minimum: 1 157 | Maximum: -1 158 | SpacesInParentheses: false 159 | SpacesInSquareBrackets: false 160 | SpaceBeforeSquareBrackets: false 161 | BitFieldColonSpacing: Both 162 | Standard: Latest 163 | StatementAttributeLikeMacros: 164 | - Q_EMIT 165 | StatementMacros: 166 | - Q_UNUSED 167 | - QT_REQUIRE_VERSION 168 | TabWidth: 4 169 | UseCRLF: false 170 | UseTab: Never 171 | WhitespaceSensitiveMacros: 172 | - STRINGIZE 173 | - PP_STRINGIZE 174 | - BOOST_PP_STRINGIZE 175 | - NS_SWIFT_NAME 176 | - CF_SWIFT_NAME 177 | --- 178 | Language: Proto 179 | BasedOnStyle: Microsoft 180 | AlignConsecutiveDeclarations: true 181 | AlignConsecutiveAssignments: true 182 | ColumnLimit: 0 183 | ... 184 | 185 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ main ] 7 | pull_request: 8 | branches: [ main ] 9 | 10 | jobs: 11 | build: 12 | strategy: 13 | matrix: 14 | include: [{ name: windows_x86, arch: x86 },{ name: windows_x64, arch: x64 }] 15 | 16 | name: ${{ matrix.name }} 17 | 18 | runs-on: windows-latest 19 | 20 | steps: 21 | - name: checkout 22 | uses: actions/checkout@v4 23 | with: 24 | submodules: 'true' 25 | 26 | - name: setup VC-LTL 27 | working-directory: ${{env.GITHUB_WORKSPACE}} 28 | run: nuget install VC-LTL 29 | 30 | - name: setup xmake 31 | uses: xmake-io/github-action-setup-xmake@v1 32 | 33 | - name: configure 34 | run: xmake f -a ${{ matrix.arch }} 35 | 36 | - name: build 37 | run: xmake 38 | 39 | - name: upload 40 | uses: actions/upload-artifact@v4 41 | with: 42 | name: ${{ matrix.name }} 43 | path: build/release 44 | -------------------------------------------------------------------------------- /.github/workflows/update-submodules.yml: -------------------------------------------------------------------------------- 1 | name: Update Submodules 2 | 3 | on: 4 | schedule: 5 | # 每6小时执行一次 6 | - cron: '0 */6 * * *' 7 | push: 8 | # 当main分支有新的推送时执行 9 | branches: 10 | - main 11 | 12 | jobs: 13 | update: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout the repository 18 | uses: actions/checkout@v2 19 | with: 20 | # 确保拉取所有子模块 21 | submodules: 'recursive' 22 | 23 | - name: setup-gh 24 | # You may pin to the exact commit or the version. 25 | # uses: wusatosi/setup-gh@7dae6c8246092454793fe35dd4d5a9e8bf8ed34d 26 | uses: wusatosi/setup-gh@v1.1 27 | with: 28 | # Repository setting for Github CLI 29 | repository: ${{ github.repository }} 30 | # Authentication token setting for Github CLI 31 | token: ${{ github.token }} 32 | 33 | - name: Init Submodules 34 | run: | 35 | git submodule init 36 | 37 | - name: Update Submodules 38 | run: | 39 | echo "Update submodule" 40 | git submodule update --remote --recursive 41 | 42 | echo "Check if submodule is changed or not" 43 | # Track submodule changes 44 | git status 45 | git diff --submodule 46 | 47 | git config --global user.email "czyt.go@gmail.com" 48 | git config --global user.name "czyt" 49 | 50 | # First, add any submodule changes. 51 | git add . || true # The '|| true' ensures that even if this command fails, the script will continue. 52 | 53 | # Now, check if there are changes to commit. We use git diff-index HEAD -- here to check for changes as compared to the last commit. 54 | if git diff-index --quiet HEAD --; then 55 | # If there are no changes then the --quiet flag will make sure the command exits with 0 and this block will be executed. 56 | echo "No changes in submodules to commit." 57 | else 58 | # If there are changes this block will be executed. 59 | echo "Submodule is changed, creating new branch" 60 | git checkout -b update-submodules-${{ github.run_id }} 61 | git commit -m "Automatically update submodules" 62 | echo "Push the new branch update-submodules-${{ github.run_id }}" 63 | git push --set-upstream origin update-submodules-${{ github.run_id }} 64 | echo "Create a PR" 65 | gh pr create --title "Auto-update submodules" --body "Automated changes to update submodules" --head update-submodules-${{ github.run_id }} --base main 66 | fi 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .xmake/ 2 | build/ 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "minhook"] 2 | path = minhook 3 | url = https://github.com/ca-x/minhook 4 | [submodule "mini_gzip"] 5 | path = mini_gzip 6 | url = https://github.com/ca-x/mini_gzip 7 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | # 基于编辑器的 HTTP 客户端请求 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vivaldi_plus.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Win32", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [ 9 | "_DEBUG", 10 | "UNICODE", 11 | "_UNICODE" 12 | ], 13 | "cStandard": "c17", 14 | "cppStandard": "c++17", 15 | "intelliSenseMode": "windows-msvc-x64" 16 | } 17 | ], 18 | "version": 4 19 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.excalidraw": "json", 4 | "*.excalidrawlib": "json", 5 | "xstring": "cpp", 6 | "algorithm": "cpp", 7 | "atomic": "cpp", 8 | "bit": "cpp", 9 | "cctype": "cpp", 10 | "cmath": "cpp", 11 | "compare": "cpp", 12 | "concepts": "cpp", 13 | "cstddef": "cpp", 14 | "cstdint": "cpp", 15 | "cstdio": "cpp", 16 | "cstdlib": "cpp", 17 | "cstring": "cpp", 18 | "ctime": "cpp", 19 | "cwchar": "cpp", 20 | "exception": "cpp", 21 | "functional": "cpp", 22 | "initializer_list": "cpp", 23 | "iosfwd": "cpp", 24 | "limits": "cpp", 25 | "list": "cpp", 26 | "memory": "cpp", 27 | "new": "cpp", 28 | "ratio": "cpp", 29 | "stop_token": "cpp", 30 | "string": "cpp", 31 | "thread": "cpp", 32 | "tuple": "cpp", 33 | "type_traits": "cpp", 34 | "typeinfo": "cpp", 35 | "unordered_map": "cpp", 36 | "utility": "cpp", 37 | "vector": "cpp", 38 | "xhash": "cpp", 39 | "xmemory": "cpp", 40 | "xstddef": "cpp", 41 | "xtr1common": "cpp", 42 | "xutility": "cpp" 43 | } 44 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 shuax 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 功能 2 | - 双击鼠标中键关闭标签页 3 | - 保留最后标签页(防止关闭最后一个标签页时关闭浏览器,点X不行) 4 | - 鼠标悬停标签栏滚动 5 | - 按住右键时滚轮滚动标签栏 6 | - 便携设计,程序放在App目录,数据放在Data目录(不兼容原版数据,可以重装系统换电脑不丢数据) 7 | - 移除更新错误警告(因为是绿色版没有自动更新功能) 8 | # 自定义 9 | 本dll提供有限的自定义选项(dll版本需要为1.5.7.0 +),新建一个名为config.ini的文件到dll同目录即可。可以使用%app%来表示dll所在目录 10 | ``` 11 | [dir_setting] 12 | data=%app%\..\Data 13 | cache=%app%\..\Cache 14 | ``` 15 | # vivaldi 安装包 16 | 请访问 https://vivaldi.czyt.tech 获取下载链接 17 | # 获取 18 | 采用GitHub Actions自动编译发布,下载地址:[Powered by nightly.link](https://nightly.link/ca-x/vivaldi_plus/workflows/build/main) 19 | 20 | [![build status](https://github.com/czyt/vivaldi_plus/actions/workflows/build.yml/badge.svg)](https://github.com/czyt/vivaldi_plus/actions/workflows/build.yml) 21 | # 安装 22 | dll放入解压版Vivaldi目录(vivaldi.exe同目录)即可 23 | ## 源项目 24 | [chromePlus](https://github.com/icy37785/chrome_plus) 25 | 26 | > 修复 Chrome 118+ 的代码参考了 [Bush2021的项目](https://github.com/Bush2021/chrome_plus),在此表示感谢。我不会cpp,所以代码写的很烂。版本号更新频繁,看不惯的可以不看。 27 | -------------------------------------------------------------------------------- /VC-LTL5.lua: -------------------------------------------------------------------------------- 1 | target("VC-LTL-5") 2 | set_kind("phony") 3 | before_build("windows", function (target) 4 | local function find_in_file() 5 | for _, dir in ipairs(os.dirs("$(projectdir)/*")) do 6 | name = dir:match(".*\\(.*)") 7 | if name:find("VC%-LTL") then 8 | return dir .. [[\build\native\]] 9 | end 10 | end 11 | end 12 | local function find_in_reg() 13 | return vformat("$(reg HKEY_CURRENT_USER\\Code\\VC-LTL;Root)") 14 | end 15 | local VC_LTL_Root = find_in_file() or find_in_reg() 16 | if #VC_LTL_Root==0 then 17 | return 18 | end 19 | local WindowsTargetPlatformMinVersion = "6.0.6000.0" 20 | cprint("${color.warning}VC-LTL Path : %s", VC_LTL_Root) 21 | cprint("${color.warning}WindowsTargetPlatformMinVersion : %s", WindowsTargetPlatformMinVersion) 22 | import("core.tool.toolchain") 23 | local msvc = toolchain.load("msvc") 24 | local runenvs = msvc:runenvs() 25 | 26 | local includepath = VC_LTL_Root .. [[TargetPlatform\header;]] .. VC_LTL_Root .. [[TargetPlatform\]] .. WindowsTargetPlatformMinVersion..[[\header;]] 27 | 28 | runenvs.INCLUDE = includepath .. runenvs.INCLUDE 29 | 30 | local arch = target:arch() 31 | local archpath = "Win32" 32 | if arch=="x86" then 33 | archpath = "Win32" 34 | elseif arch=="x64" then 35 | archpath = "x64" 36 | end 37 | cprint("${color.warning}Platform : %s", archpath) 38 | local libpath = VC_LTL_Root .. [[TargetPlatform\]] .. WindowsTargetPlatformMinVersion..[[\lib\]] .. archpath .. ";" 39 | 40 | runenvs.LIB = libpath .. runenvs.LIB 41 | 42 | -- print(runenvs.INCLUDE) 43 | -- print(runenvs.LIB) 44 | end) 45 | -------------------------------------------------------------------------------- /src/PakFile.h: -------------------------------------------------------------------------------- 1 | #pragma warning(disable : 4334) 2 | #pragma warning(disable : 4267) 3 | 4 | extern "C" 5 | { 6 | #include "..\mini_gzip\miniz.c" 7 | #include "..\mini_gzip\mini_gzip.h" 8 | #include "..\mini_gzip\mini_gzip.c" 9 | } 10 | 11 | #pragma pack(push) 12 | #pragma pack(1) 13 | 14 | #define PACK4_FILE_VERSION (4) 15 | #define PACK5_FILE_VERSION (5) 16 | 17 | struct PAK4_HEADER 18 | { 19 | uint32_t num_entries; 20 | uint8_t encodeing; 21 | }; 22 | 23 | struct PAK5_HEADER 24 | { 25 | uint32_t encodeing; 26 | uint16_t resource_count; 27 | uint16_t alias_count; 28 | }; 29 | 30 | struct PAK_ENTRY 31 | { 32 | uint16_t resource_id; 33 | uint32_t file_offset; 34 | }; 35 | 36 | struct PAK_ALIAS 37 | { 38 | uint16_t resource_id; 39 | uint16_t entry_index; 40 | }; 41 | #pragma pack(pop) 42 | 43 | bool CheckHeader(uint8_t *buffer, PAK_ENTRY *&pak_entry, PAK_ENTRY *&end_entry) 44 | { 45 | uint32_t version = *(uint32_t *)buffer; 46 | 47 | if (version != PACK4_FILE_VERSION && version != PACK5_FILE_VERSION) 48 | return false; 49 | 50 | if (version == PACK4_FILE_VERSION) 51 | { 52 | PAK4_HEADER *pak_header = (PAK4_HEADER *)(buffer + sizeof(uint32_t)); 53 | if (pak_header->encodeing != 1) 54 | return false; 55 | 56 | pak_entry = (PAK_ENTRY *)(buffer + sizeof(uint32_t) + sizeof(PAK4_HEADER)); 57 | end_entry = pak_entry + pak_header->num_entries; 58 | } 59 | 60 | if (version == PACK5_FILE_VERSION) 61 | { 62 | PAK5_HEADER *pak_header = (PAK5_HEADER *)(buffer + sizeof(uint32_t)); 63 | if (pak_header->encodeing != 1) 64 | return false; 65 | 66 | pak_entry = (PAK_ENTRY *)(buffer + sizeof(uint32_t) + sizeof(PAK5_HEADER)); 67 | end_entry = pak_entry + pak_header->resource_count; 68 | } 69 | 70 | // 为了保存最后一条的"下一条",这条特殊的条目的id一定为0 71 | if (!end_entry || end_entry->resource_id != 0) 72 | return false; 73 | 74 | return true; 75 | } 76 | 77 | template 78 | void PakFind(uint8_t *buffer, uint8_t *pos, Function f) 79 | { 80 | PAK_ENTRY *pak_entry = NULL; 81 | PAK_ENTRY *end_entry = NULL; 82 | 83 | // 检查文件头 84 | if (!CheckHeader(buffer, pak_entry, end_entry)) 85 | return; 86 | 87 | do 88 | { 89 | PAK_ENTRY *next_entry = pak_entry + 1; 90 | if (pos >= buffer + pak_entry->file_offset && pos <= buffer + next_entry->file_offset) 91 | { 92 | f(buffer + pak_entry->file_offset, next_entry->file_offset - pak_entry->file_offset); 93 | break; 94 | } 95 | 96 | pak_entry = next_entry; 97 | } while (pak_entry->resource_id != 0); 98 | } 99 | 100 | template 101 | void TraversalGZIPFile(uint8_t *buffer, Function f) 102 | { 103 | PAK_ENTRY *pak_entry = NULL; 104 | PAK_ENTRY *end_entry = NULL; 105 | 106 | // 检查文件头 107 | if (!CheckHeader(buffer, pak_entry, end_entry)) 108 | return; 109 | 110 | do 111 | { 112 | PAK_ENTRY *next_entry = pak_entry + 1; 113 | uint32_t old_size = next_entry->file_offset - pak_entry->file_offset; 114 | 115 | if (old_size < 10 * 1024) 116 | { 117 | // 小于10k文件跳过 118 | pak_entry = next_entry; 119 | continue; 120 | } 121 | 122 | BYTE gzip[] = {0x1F, 0x8B, 0x08}; 123 | size_t gzip_len = sizeof(gzip); 124 | if (memcmp(buffer + pak_entry->file_offset, gzip, gzip_len) != 0) 125 | { 126 | // 不是gzip文件跳过 127 | pak_entry = next_entry; 128 | continue; 129 | } 130 | 131 | uint32_t original_size = *(uint32_t *)(buffer + next_entry->file_offset - 4); 132 | uint8_t *unpack_buffer = (uint8_t *)malloc(original_size); 133 | if (!unpack_buffer) 134 | return; 135 | 136 | struct mini_gzip gz; 137 | mini_gz_start(&gz, buffer + pak_entry->file_offset, old_size); 138 | int unpack_len = mini_gz_unpack(&gz, unpack_buffer, original_size); 139 | 140 | if (original_size == unpack_len) 141 | { 142 | uint32_t new_len = old_size; 143 | bool changed = f(unpack_buffer, unpack_len, new_len); 144 | if (changed) 145 | { 146 | // 如果有改变 147 | size_t compress_size = 0; 148 | uint8_t *compress_buffer = (uint8_t *)gzip_compress(unpack_buffer, new_len, &compress_size); 149 | if (compress_buffer && compress_size < old_size) 150 | { 151 | /*FILE *fp = fopen("test.gz", "wb"); 152 | fwrite(compress_buffer, compress_size, 1, fp); 153 | fclose(fp);*/ 154 | 155 | // gzip头 156 | memcpy(buffer + pak_entry->file_offset, compress_buffer, 10); 157 | 158 | // extra 159 | buffer[pak_entry->file_offset + 3] = 0x04; 160 | uint16_t extra_length = old_size - compress_size - 2; 161 | memcpy(buffer + pak_entry->file_offset + 10, &extra_length, sizeof(extra_length)); 162 | memset(buffer + pak_entry->file_offset + 12, '\0', extra_length); 163 | 164 | // compress 165 | memcpy(buffer + pak_entry->file_offset + 12 + extra_length, compress_buffer + 10, compress_size - 10); 166 | 167 | /*fp = fopen("test2.gz", "wb"); 168 | fwrite(buffer + pak_entry->file_offset, old_size, 1, fp); 169 | fclose(fp);*/ 170 | } 171 | else 172 | { 173 | DebugLog(L"gzip compress error %d %d", compress_size, old_size); 174 | } 175 | 176 | if (compress_buffer) 177 | free(compress_buffer); 178 | } 179 | } 180 | 181 | free(unpack_buffer); 182 | pak_entry = next_entry; 183 | } while (pak_entry->resource_id != 0); 184 | } 185 | -------------------------------------------------------------------------------- /src/PakPatch.h: -------------------------------------------------------------------------------- 1 | #include "PakFile.h" 2 | 3 | DWORD resources_pak_size = 0; 4 | 5 | HANDLE resources_pak_map = NULL; 6 | 7 | typedef HANDLE(WINAPI *pMapViewOfFile)( 8 | _In_ HANDLE hFileMappingObject, 9 | _In_ DWORD dwDesiredAccess, 10 | _In_ DWORD dwFileOffsetHigh, 11 | _In_ DWORD dwFileOffsetLow, 12 | _In_ SIZE_T dwNumberOfBytesToMap); 13 | 14 | pMapViewOfFile RawMapViewOfFile = NULL; 15 | 16 | HANDLE WINAPI MyMapViewOfFile( 17 | _In_ HANDLE hFileMappingObject, 18 | _In_ DWORD dwDesiredAccess, 19 | _In_ DWORD dwFileOffsetHigh, 20 | _In_ DWORD dwFileOffsetLow, 21 | _In_ SIZE_T dwNumberOfBytesToMap) 22 | { 23 | if (hFileMappingObject == resources_pak_map) 24 | { 25 | // 修改属性为可修改 26 | LPVOID buffer = RawMapViewOfFile(hFileMappingObject, FILE_MAP_COPY, dwFileOffsetHigh, 27 | dwFileOffsetLow, dwNumberOfBytesToMap); 28 | 29 | // 不再需要hook 30 | resources_pak_map = NULL; 31 | MH_DisableHook(MapViewOfFile); 32 | 33 | if (buffer) 34 | { 35 | // 遍历gzip文件 36 | TraversalGZIPFile((BYTE *)buffer, [=](uint8_t *begin, uint32_t size, uint32_t &new_len) { 37 | bool changed = false; 38 | 39 | BYTE search_start[] = R"()"; 40 | uint8_t *pos = memmem(begin, size, search_start, sizeof(search_start) - 1); 41 | if (pos) 42 | { 43 | 44 | // 压缩HTML以备写入补丁信息 45 | std::string html((char *)begin, size); 46 | compression_html(html); 47 | 48 | // RemoveUpdateError 49 | // if (IsNeedPortable()) 50 | { 51 | ReplaceStringInPlace(html, R"(hidden="[[!showUpdateStatus_]]")", R"(hidden="true")"); 52 | ReplaceStringInPlace(html, R"(hidden="[[!shouldShowIcons_(showUpdateStatus_)]]")", R"(hidden="true")"); 53 | } 54 | 55 | const char prouct_title[] = u8R"({aboutBrowserVersion}
Vivaldi++ )" RELEASE_VER_STR u8R"( inside
)"; 56 | ReplaceStringInPlace(html, R"({aboutBrowserVersion})", prouct_title); 57 | 58 | if (html.length() <= size) 59 | { 60 | // 写入修改 61 | memcpy(begin, html.c_str(), html.length()); 62 | 63 | // 修改长度 64 | new_len = html.length(); 65 | changed = true; 66 | } 67 | } 68 | 69 | return changed; 70 | }); 71 | } 72 | 73 | return buffer; 74 | } 75 | 76 | return RawMapViewOfFile(hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh, 77 | dwFileOffsetLow, dwNumberOfBytesToMap); 78 | } 79 | 80 | HANDLE resources_pak_file = NULL; 81 | 82 | typedef HANDLE(WINAPI *pCreateFileMapping)( 83 | _In_ HANDLE hFile, 84 | _In_opt_ LPSECURITY_ATTRIBUTES lpAttributes, 85 | _In_ DWORD flProtect, 86 | _In_ DWORD dwMaximumSizeHigh, 87 | _In_ DWORD dwMaximumSizeLow, 88 | _In_opt_ LPCTSTR lpName); 89 | 90 | pCreateFileMapping RawCreateFileMapping = NULL; 91 | 92 | HANDLE WINAPI MyCreateFileMapping( 93 | _In_ HANDLE hFile, 94 | _In_opt_ LPSECURITY_ATTRIBUTES lpAttributes, 95 | _In_ DWORD flProtect, 96 | _In_ DWORD dwMaximumSizeHigh, 97 | _In_ DWORD dwMaximumSizeLow, 98 | _In_opt_ LPCTSTR lpName) 99 | { 100 | if (hFile == resources_pak_file) 101 | { 102 | // 修改属性为可修改 103 | resources_pak_map = RawCreateFileMapping(hFile, lpAttributes, PAGE_WRITECOPY, 104 | dwMaximumSizeHigh, dwMaximumSizeLow, lpName); 105 | 106 | // 不再需要hook 107 | resources_pak_file = NULL; 108 | MH_DisableHook(CreateFileMappingW); 109 | 110 | if (MH_CreateHook(MapViewOfFile, MyMapViewOfFile, (LPVOID *)&RawMapViewOfFile) == MH_OK) 111 | { 112 | MH_EnableHook(MapViewOfFile); 113 | } 114 | 115 | return resources_pak_map; 116 | } 117 | return RawCreateFileMapping(hFile, lpAttributes, flProtect, dwMaximumSizeHigh, 118 | dwMaximumSizeLow, lpName); 119 | } 120 | 121 | typedef HANDLE(WINAPI *pCreateFile)( 122 | _In_ LPCTSTR lpFileName, 123 | _In_ DWORD dwDesiredAccess, 124 | _In_ DWORD dwShareMode, 125 | _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, 126 | _In_ DWORD dwCreationDisposition, 127 | _In_ DWORD dwFlagsAndAttributes, 128 | _In_opt_ HANDLE hTemplateFile); 129 | 130 | pCreateFile RawCreateFile = NULL; 131 | 132 | HANDLE WINAPI MyCreateFile( 133 | _In_ LPCTSTR lpFileName, 134 | _In_ DWORD dwDesiredAccess, 135 | _In_ DWORD dwShareMode, 136 | _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, 137 | _In_ DWORD dwCreationDisposition, 138 | _In_ DWORD dwFlagsAndAttributes, 139 | _In_opt_ HANDLE hTemplateFile) 140 | { 141 | HANDLE file = RawCreateFile(lpFileName, dwDesiredAccess, dwShareMode, 142 | lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, 143 | hTemplateFile); 144 | 145 | if (isEndWith(lpFileName, L"resources.pak")) 146 | { 147 | resources_pak_file = file; 148 | resources_pak_size = GetFileSize(resources_pak_file, NULL); 149 | 150 | if (MH_CreateHook(CreateFileMappingW, MyCreateFileMapping, (LPVOID *)&RawCreateFileMapping) == MH_OK) 151 | { 152 | MH_EnableHook(CreateFileMappingW); 153 | } 154 | 155 | // 不再需要hook 156 | MH_DisableHook(CreateFileW); 157 | } 158 | 159 | return file; 160 | } 161 | 162 | void PakPatch() 163 | { 164 | MH_STATUS status = MH_CreateHook(CreateFileW, MyCreateFile, (LPVOID *)&RawCreateFile); 165 | if (status == MH_OK) 166 | { 167 | MH_EnableHook(CreateFileW); 168 | } 169 | else 170 | { 171 | DebugLog(L"MH_CreateHook CreateFileW failed:%d", status); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/TabBookmark.h: -------------------------------------------------------------------------------- 1 | #include 2 | #pragma comment(lib, "oleacc.lib") 3 | 4 | #include 5 | #include 6 | 7 | using NodePtr = Microsoft::WRL::ComPtr; 8 | 9 | #define KEY_PRESSED 0x8000 10 | bool IsPressed(int key) 11 | { 12 | return key && (::GetKeyState(key) & KEY_PRESSED) != 0; 13 | } 14 | 15 | long GetAccessibleRole(NodePtr node) 16 | { 17 | VARIANT self; 18 | self.vt = VT_I4; 19 | self.lVal = CHILDID_SELF; 20 | 21 | VARIANT role; 22 | if (S_OK == node->get_accRole(self, &role)) 23 | { 24 | if (role.vt == VT_I4) 25 | { 26 | return role.lVal; 27 | } 28 | } 29 | return 0; 30 | } 31 | 32 | long GetAccessibleState(NodePtr node) 33 | { 34 | VARIANT self; 35 | self.vt = VT_I4; 36 | self.lVal = CHILDID_SELF; 37 | 38 | VARIANT state; 39 | if (S_OK == node->get_accState(self, &state)) 40 | { 41 | if (state.vt == VT_I4) 42 | { 43 | return state.lVal; 44 | } 45 | } 46 | return 0; 47 | } 48 | 49 | template 50 | void TraversalAccessible(NodePtr node, Function f) 51 | { 52 | long childCount = 0; 53 | if (node && S_OK == node->get_accChildCount(&childCount) && childCount) 54 | { 55 | std::unique_ptr varChildren(new VARIANT[childCount]); 56 | if (S_OK == AccessibleChildren(node.Get(), 0, childCount, varChildren.get(), &childCount)) 57 | { 58 | for (int i = 0; i < childCount; i++) 59 | { 60 | if (varChildren[i].vt == VT_DISPATCH) 61 | { 62 | Microsoft::WRL::ComPtr dispatch = varChildren[i].pdispVal; 63 | NodePtr child = nullptr; 64 | if (S_OK == dispatch->QueryInterface(IID_IAccessible, (void **)&child)) 65 | { 66 | if ((GetAccessibleState(child) & STATE_SYSTEM_INVISIBLE) == 0) // 只遍历可见节点 67 | { 68 | if (f(child)) 69 | { 70 | break; 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | template 81 | void TraversalRawAccessible(NodePtr node, Function f) 82 | { 83 | long childCount = 0; 84 | if (node && S_OK == node->get_accChildCount(&childCount) && childCount) 85 | { 86 | std::unique_ptr varChildren(new VARIANT[childCount]); 87 | if (S_OK == AccessibleChildren(node.Get(), 0, childCount, varChildren.get(), &childCount)) 88 | { 89 | for (int i = 0; i < childCount; i++) 90 | { 91 | if (varChildren[i].vt == VT_DISPATCH) 92 | { 93 | Microsoft::WRL::ComPtr dispatch = varChildren[i].pdispVal; 94 | NodePtr child = nullptr; 95 | if (S_OK == dispatch->QueryInterface(IID_IAccessible, (void **)&child)) 96 | { 97 | // if ((GetAccessibleState(child) & STATE_SYSTEM_INVISIBLE) == 0) // 只遍历可见节点 98 | { 99 | if (f(child)) 100 | { 101 | break; 102 | } 103 | } 104 | } 105 | } 106 | } 107 | } 108 | } 109 | } 110 | 111 | NodePtr FindPageTabList(NodePtr node) 112 | { 113 | NodePtr PageTabList = nullptr; 114 | if (node) 115 | { 116 | TraversalAccessible(node, [&](NodePtr child) { 117 | long role = GetAccessibleRole(child); 118 | if (role == ROLE_SYSTEM_PAGETABLIST) 119 | { 120 | PageTabList = child; 121 | } 122 | else if (role == ROLE_SYSTEM_PANE || role == ROLE_SYSTEM_TOOLBAR) 123 | { 124 | PageTabList = FindPageTabList(child); 125 | } 126 | return PageTabList; 127 | }); 128 | } 129 | return PageTabList; 130 | } 131 | 132 | NodePtr FindPageTab(NodePtr node) 133 | { 134 | NodePtr PageTab = nullptr; 135 | if (node) 136 | { 137 | TraversalAccessible(node, [&](NodePtr child) { 138 | long role = GetAccessibleRole(child); 139 | if (role == ROLE_SYSTEM_PAGETAB) 140 | { 141 | PageTab = child; 142 | } 143 | else if (role == ROLE_SYSTEM_PANE || role == ROLE_SYSTEM_TOOLBAR) 144 | { 145 | PageTab = FindPageTab(child); 146 | } 147 | return PageTab; 148 | }); 149 | } 150 | return PageTab; 151 | } 152 | 153 | NodePtr GetParentElement(NodePtr child) 154 | { 155 | NodePtr element = nullptr; 156 | Microsoft::WRL::ComPtr dispatch = nullptr; 157 | if (S_OK == child->get_accParent(&dispatch) && dispatch) 158 | { 159 | NodePtr parent = nullptr; 160 | if (S_OK == dispatch->QueryInterface(IID_IAccessible, (void **)&parent)) 161 | { 162 | element = parent; 163 | } 164 | } 165 | return element; 166 | } 167 | 168 | NodePtr GetTopContainerView(HWND hwnd) 169 | { 170 | NodePtr TopContainerView = nullptr; 171 | wchar_t name[MAX_PATH]; 172 | if (GetClassName(hwnd, name, MAX_PATH) && wcscmp(name, L"Chrome_WidgetWin_1") == 0) 173 | { 174 | NodePtr paccMainWindow = nullptr; 175 | if (S_OK == AccessibleObjectFromWindow(hwnd, OBJID_WINDOW, IID_PPV_ARGS(&paccMainWindow))) 176 | { 177 | NodePtr PageTabList = FindPageTabList(paccMainWindow); 178 | if (PageTabList) 179 | { 180 | TopContainerView = GetParentElement(PageTabList); 181 | } 182 | if (!TopContainerView) 183 | { 184 | DebugLog(L"FindTopContainerView failed"); 185 | } 186 | } 187 | } 188 | return TopContainerView; 189 | } 190 | 191 | NodePtr FindChildElement(NodePtr parent, long role, int skipcount = 0) 192 | { 193 | NodePtr element = nullptr; 194 | if (parent) 195 | { 196 | int i = 0; 197 | TraversalAccessible(parent, [&element, &role, &i, &skipcount](NodePtr child) { 198 | // DebugLog(L"当前 %d,%d", i, skipcount); 199 | if (GetAccessibleRole(child) == role) 200 | { 201 | if (i == skipcount) 202 | { 203 | element = child; 204 | } 205 | i++; 206 | } 207 | return element != nullptr; 208 | }); 209 | } 210 | return element; 211 | } 212 | 213 | template 214 | void GetAccessibleSize(NodePtr node, Function f) 215 | { 216 | VARIANT self; 217 | self.vt = VT_I4; 218 | self.lVal = CHILDID_SELF; 219 | 220 | RECT rect; 221 | if (S_OK == node->accLocation(&rect.left, &rect.top, &rect.right, &rect.bottom, self)) 222 | { 223 | // rect.left = (int)((float)rect.left); 224 | // rect.top = (int)((float)rect.top); 225 | // rect.right = (int)((float)rect.right); 226 | // rect.bottom = (int)((float)rect.bottom); 227 | 228 | rect.right += rect.left; 229 | rect.bottom += rect.top; 230 | 231 | f(rect); 232 | } 233 | } 234 | 235 | // 鼠标是否在某个标签上 236 | bool IsOnOneTab(NodePtr top, POINT pt) 237 | { 238 | bool flag = false; 239 | NodePtr PageTabList = FindPageTabList(top); 240 | if (PageTabList) 241 | { 242 | NodePtr PageTab = FindPageTab(PageTabList); 243 | if (PageTab) 244 | { 245 | NodePtr PageTabPane = GetParentElement(PageTab); 246 | if (PageTabPane) 247 | { 248 | TraversalAccessible(PageTabPane, [&flag, &pt](NodePtr child) { 249 | if (GetAccessibleRole(child) == ROLE_SYSTEM_PAGETAB) 250 | { 251 | GetAccessibleSize(child, [&flag, &pt](RECT rect) { 252 | if (PtInRect(&rect, pt)) 253 | { 254 | flag = true; 255 | } 256 | }); 257 | } 258 | return flag; 259 | }); 260 | } 261 | } 262 | } 263 | else 264 | { 265 | // if (top) DebugLog(L"IsOnOneTab failed"); 266 | } 267 | return flag; 268 | } 269 | 270 | // 鼠标是否在某个未激活标签上 271 | bool IsOnOneInactiveTab(NodePtr top, POINT pt) 272 | { 273 | bool flag = false; 274 | NodePtr PageTabList = FindPageTabList(top); 275 | if (PageTabList) 276 | { 277 | NodePtr PageTab = FindPageTab(PageTabList); 278 | if (PageTab) 279 | { 280 | NodePtr PageTabPane = GetParentElement(PageTab); 281 | if (PageTabPane) 282 | { 283 | TraversalAccessible(PageTabPane, [&flag, &pt](NodePtr child) { 284 | if (GetAccessibleRole(child) == ROLE_SYSTEM_PAGETAB && !(GetAccessibleState(child) & STATE_SYSTEM_SELECTED)) 285 | { 286 | GetAccessibleSize(child, [&flag, &pt](RECT rect) { 287 | if (PtInRect(&rect, pt)) 288 | { 289 | flag = true; 290 | } 291 | }); 292 | } 293 | return flag; 294 | }); 295 | } 296 | } 297 | } 298 | else 299 | { 300 | // if (top) DebugLog(L"IsOnOneTab failed"); 301 | } 302 | return flag; 303 | } 304 | 305 | // 是否只有一个标签 306 | bool IsOnlyOneTab(NodePtr top) 307 | { 308 | NodePtr PageTabList = FindPageTabList(top); 309 | if (PageTabList) 310 | { 311 | // DebugLog(L"IsOnlyOneTab"); 312 | long tab_count = 0; 313 | 314 | NodePtr PageTab = FindPageTab(PageTabList); 315 | if (PageTab) 316 | { 317 | NodePtr PageTabPane = GetParentElement(PageTab); 318 | if (PageTabPane) 319 | { 320 | TraversalAccessible(PageTabPane, [&tab_count](NodePtr child) { 321 | // if (GetAccessibleRole(child) == ROLE_SYSTEM_PAGETAB && GetChildCount(child)) 322 | if (GetAccessibleRole(child) == ROLE_SYSTEM_PAGETAB) 323 | { 324 | tab_count++; 325 | } 326 | return false; 327 | }); 328 | } 329 | } 330 | // DebugLog(L"closing %d,%d", closing, tab_count); 331 | return tab_count <= 1; 332 | } 333 | else 334 | { 335 | // if (top) DebugLog(L"IsOnlyOneTab failed"); 336 | } 337 | return false; 338 | } 339 | 340 | // 鼠标是否在标签栏上 341 | bool IsOnTheTab(NodePtr top, POINT pt) 342 | { 343 | bool flag = false; 344 | NodePtr PageTabList = FindPageTabList(top); 345 | if (PageTabList) 346 | { 347 | GetAccessibleSize(PageTabList, [&flag, &pt](RECT rect) { 348 | if (PtInRect(&rect, pt)) 349 | { 350 | flag = true; 351 | } 352 | }); 353 | } 354 | else 355 | { 356 | // if (top) DebugLog(L"IsOnTheTab failed"); 357 | } 358 | return flag; 359 | } 360 | 361 | bool IsNeedKeep() 362 | { 363 | bool keep_tab = false; 364 | 365 | NodePtr TopContainerView = GetTopContainerView(GetForegroundWindow()); 366 | if (IsOnlyOneTab(TopContainerView)) 367 | { 368 | keep_tab = true; 369 | } 370 | 371 | if (TopContainerView) 372 | { 373 | } 374 | 375 | return keep_tab; 376 | } 377 | 378 | HHOOK mouse_hook = NULL; 379 | LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) 380 | { 381 | static bool wheel_tab_ing = false; 382 | static bool double_click_ing = false; 383 | static bool close_tab_ing = false; 384 | 385 | bool close_tab = false; 386 | bool keep_tab = false; 387 | 388 | if (nCode != HC_ACTION) 389 | { 390 | return CallNextHookEx(mouse_hook, nCode, wParam, lParam); 391 | } 392 | 393 | if (nCode == HC_ACTION) 394 | { 395 | PMOUSEHOOKSTRUCT pmouse = (PMOUSEHOOKSTRUCT)lParam; 396 | 397 | if (wParam == WM_RBUTTONUP && wheel_tab_ing) 398 | { 399 | // DebugLog(L"wheel_tab_ing"); 400 | wheel_tab_ing = false; 401 | return 1; 402 | } 403 | 404 | if (wParam == WM_MOUSEMOVE || wParam == WM_NCMOUSEMOVE) 405 | { 406 | 407 | return CallNextHookEx(mouse_hook, nCode, wParam, lParam); 408 | } 409 | 410 | if (pmouse->dwExtraInfo == MAGIC_CODE) 411 | { 412 | // DebugLog(L"MAGIC_CODE %x", wParam); 413 | goto next; 414 | } 415 | 416 | HWND hwnd = WindowFromPoint(pmouse->pt); 417 | NodePtr TopContainerView = GetTopContainerView(hwnd); 418 | bool isOnOneTab = IsOnOneTab(TopContainerView, pmouse->pt); 419 | bool isOnlyOneTab = IsOnlyOneTab(TopContainerView); 420 | // 鼠标滚动事件 421 | if (wParam == WM_MOUSEWHEEL) 422 | { 423 | PMOUSEHOOKSTRUCTEX pwheel = (PMOUSEHOOKSTRUCTEX)lParam; 424 | int zDelta = GET_WHEEL_DELTA_WPARAM(pwheel->mouseData); 425 | 426 | if (isOnOneTab || IsPressed(VK_RBUTTON)) 427 | { 428 | hwnd = GetTopWnd(hwnd); 429 | if (zDelta > 0) 430 | { 431 | ExecuteCommand(IDC_SELECT_PREVIOUS_TAB, hwnd); 432 | } 433 | else 434 | { 435 | ExecuteCommand(IDC_SELECT_NEXT_TAB, hwnd); 436 | } 437 | 438 | wheel_tab_ing = true; 439 | return 1; 440 | } 441 | } 442 | // 在标签栏上 且为双击或右键 443 | if (isOnOneTab && ((wParam == WM_LBUTTONDBLCLK) || 444 | (EnableRightClickCloseTab && !isOnlyOneTab && 445 | wParam == WM_RBUTTONUP && !IsPressed(VK_SHIFT)))) 446 | { 447 | 448 | close_tab = true; 449 | } 450 | 451 | if (close_tab && isOnlyOneTab) 452 | { 453 | keep_tab = true; 454 | } 455 | 456 | if (wParam == WM_MBUTTONUP) 457 | { 458 | if (close_tab_ing) 459 | { 460 | close_tab_ing = false; 461 | } 462 | else 463 | { 464 | if (isOnlyOneTab) 465 | { 466 | keep_tab = true; 467 | close_tab = true; 468 | } 469 | } 470 | } 471 | 472 | if (keep_tab) 473 | { 474 | ExecuteCommand(IDC_NEW_TAB); 475 | ExecuteCommand(IDC_SELECT_PREVIOUS_TAB); 476 | } 477 | 478 | if (close_tab) 479 | { 480 | ExecuteCommand(IDC_CLOSE_TAB); 481 | close_tab_ing = true; 482 | return 1; 483 | } 484 | } 485 | next: 486 | // DebugLog(L"CallNextHookEx %X", wParam); 487 | return CallNextHookEx(mouse_hook, nCode, wParam, lParam); 488 | } 489 | 490 | HHOOK keyboard_hook = NULL; 491 | LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) 492 | { 493 | if (nCode == HC_ACTION && !(lParam & 0x80000000)) // pressed 494 | { 495 | bool keep_tab = false; 496 | 497 | if (wParam == 'W' && IsPressed(VK_CONTROL) && !IsPressed(VK_SHIFT)) 498 | { 499 | keep_tab = IsNeedKeep(); 500 | } 501 | if (wParam == VK_F4 && IsPressed(VK_CONTROL)) 502 | { 503 | keep_tab = IsNeedKeep(); 504 | } 505 | 506 | if (keep_tab) 507 | { 508 | ExecuteCommand(IDC_NEW_TAB); 509 | ExecuteCommand(IDC_SELECT_PREVIOUS_TAB); 510 | ExecuteCommand(IDC_CLOSE_TAB); 511 | return 1; 512 | } 513 | } 514 | return CallNextHookEx(keyboard_hook, nCode, wParam, lParam); 515 | } 516 | 517 | void TabBookmark() 518 | { 519 | mouse_hook = SetWindowsHookEx(WH_MOUSE, MouseProc, hInstance, GetCurrentThreadId()); 520 | keyboard_hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, hInstance, GetCurrentThreadId()); 521 | } 522 | -------------------------------------------------------------------------------- /src/appid.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef HRESULT(WINAPI *pPSStringFromPropertyKey)( 6 | REFPROPERTYKEY pkey, 7 | LPWSTR psz, 8 | UINT cch); 9 | pPSStringFromPropertyKey RawPSStringFromPropertyKey = nullptr; 10 | 11 | HRESULT WINAPI MyPSStringFromPropertyKey( 12 | REFPROPERTYKEY pkey, 13 | LPWSTR psz, 14 | UINT cch) 15 | { 16 | HRESULT result = RawPSStringFromPropertyKey(pkey, psz, cch); 17 | if (SUCCEEDED(result)) 18 | { 19 | if (pkey == PKEY_AppUserModel_ID) 20 | { 21 | // DebugLog(L"MyPSStringFromPropertyKey %s", psz); 22 | return -1; 23 | } 24 | } 25 | return result; 26 | } 27 | 28 | void SetAppId() 29 | { 30 | HMODULE Propsys = LoadLibrary(L"Propsys.dll"); 31 | 32 | PBYTE PSStringFromPropertyKey = (PBYTE)GetProcAddress(Propsys, "PSStringFromPropertyKey"); 33 | MH_STATUS status = MH_CreateHook(PSStringFromPropertyKey, MyPSStringFromPropertyKey, (LPVOID *)&RawPSStringFromPropertyKey); 34 | if (status == MH_OK) 35 | { 36 | MH_EnableHook(PSStringFromPropertyKey); 37 | } 38 | else 39 | { 40 | DebugLog(L"MH_CreateHook PSStringFromPropertyKey failed:%d", status); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/fastsearch.h: -------------------------------------------------------------------------------- 1 | #ifndef FAST_SEARCH_H_ 2 | #define FAST_SEARCH_H_ 3 | 4 | #include 5 | 6 | static const uint8_t *ForceSearch(const uint8_t *s, int n, const uint8_t *p) 7 | { 8 | int i; 9 | for (i = 0; i < n; i++) 10 | { 11 | if (*(s + i) == *p) 12 | { 13 | return s + i; 14 | } 15 | } 16 | 17 | return NULL; 18 | } 19 | 20 | static const uint8_t *SundaySearch(const uint8_t *s, int n, const uint8_t *p, int m) 21 | { 22 | int i, j; 23 | 24 | size_t skip[256]; 25 | 26 | for (i = 0; i < 256; i++) 27 | { 28 | skip[i] = m + 1; 29 | } 30 | 31 | for (i = 0; i < m; i++) 32 | { 33 | skip[p[i]] = m - i; 34 | } 35 | 36 | i = 0; 37 | while (i <= n - m) 38 | { 39 | j = 0; 40 | while (s[i + j] == p[j]) 41 | { 42 | j++; 43 | if (j >= m) 44 | { 45 | return s + i; 46 | } 47 | } 48 | 49 | i += skip[s[i + m]]; 50 | } 51 | 52 | return NULL; 53 | } 54 | 55 | const uint8_t *FastSearch(const uint8_t *s, int n, const uint8_t *p, int m) 56 | { 57 | if (!s || !p || n < m) 58 | return NULL; 59 | 60 | if (m == 0) 61 | { 62 | return s; 63 | } 64 | else if (m == 1) 65 | { 66 | return ForceSearch(s, n, p); 67 | } 68 | 69 | return SundaySearch(s, n, p, m); 70 | } 71 | 72 | #endif // FAST_SEARCH_H_ 73 | -------------------------------------------------------------------------------- /src/featuresFlag.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by czyt on 2023/10/12. 3 | // 4 | 5 | // This file is used to control the features of the browser. 6 | // whether to open the new tab page when clicking the right mouse button. 7 | 8 | bool EnableRightClickCloseTab = false; 9 | 10 | void ParseFeatureFlags() 11 | { 12 | std::wstring configFilePath = GetAppDir() + L"\\config.ini"; 13 | 14 | if (PathFileExists(configFilePath.c_str())) 15 | { 16 | // Read the config file. 17 | EnableRightClickCloseTab = GetPrivateProfileIntW(L"features", L"right_click_close_tab", 1, configFilePath.c_str()) == 1; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/green.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | BOOL WINAPI FakeGetComputerName( 4 | _Out_ LPTSTR lpBuffer, 5 | _Inout_ LPDWORD lpnSize) 6 | { 7 | return 0; 8 | } 9 | 10 | BOOL WINAPI FakeGetVolumeInformation( 11 | _In_opt_ LPCTSTR lpRootPathName, 12 | _Out_opt_ LPTSTR lpVolumeNameBuffer, 13 | _In_ DWORD nVolumeNameSize, 14 | _Out_opt_ LPDWORD lpVolumeSerialNumber, 15 | _Out_opt_ LPDWORD lpMaximumComponentLength, 16 | _Out_opt_ LPDWORD lpFileSystemFlags, 17 | _Out_opt_ LPTSTR lpFileSystemNameBuffer, 18 | _In_ DWORD nFileSystemNameSize) 19 | { 20 | return 0; 21 | } 22 | 23 | BOOL WINAPI MyCryptProtectData( 24 | _In_ DATA_BLOB *pDataIn, 25 | _In_opt_ LPCWSTR szDataDescr, 26 | _In_opt_ DATA_BLOB *pOptionalEntropy, 27 | _Reserved_ PVOID pvReserved, 28 | _In_opt_ CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct, 29 | _In_ DWORD dwFlags, 30 | _Out_ DATA_BLOB *pDataOut) 31 | { 32 | pDataOut->cbData = pDataIn->cbData; 33 | pDataOut->pbData = (BYTE *)LocalAlloc(LMEM_FIXED, pDataOut->cbData); 34 | memcpy(pDataOut->pbData, pDataIn->pbData, pDataOut->cbData); 35 | return true; 36 | } 37 | 38 | typedef BOOL(WINAPI *pCryptUnprotectData)( 39 | _In_ DATA_BLOB *pDataIn, 40 | _Out_opt_ LPWSTR *ppszDataDescr, 41 | _In_opt_ DATA_BLOB *pOptionalEntropy, 42 | _Reserved_ PVOID pvReserved, 43 | _In_opt_ CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct, 44 | _In_ DWORD dwFlags, 45 | _Out_ DATA_BLOB *pDataOut); 46 | 47 | pCryptUnprotectData RawCryptUnprotectData = NULL; 48 | 49 | BOOL WINAPI MyCryptUnprotectData( 50 | _In_ DATA_BLOB *pDataIn, 51 | _Out_opt_ LPWSTR *ppszDataDescr, 52 | _In_opt_ DATA_BLOB *pOptionalEntropy, 53 | _Reserved_ PVOID pvReserved, 54 | _In_opt_ CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct, 55 | _In_ DWORD dwFlags, 56 | _Out_ DATA_BLOB *pDataOut) 57 | { 58 | if (RawCryptUnprotectData(pDataIn, ppszDataDescr, pOptionalEntropy, pvReserved, pPromptStruct, dwFlags, pDataOut)) 59 | { 60 | return true; 61 | } 62 | 63 | pDataOut->cbData = pDataIn->cbData; 64 | pDataOut->pbData = (BYTE *)LocalAlloc(LMEM_FIXED, pDataOut->cbData); 65 | memcpy(pDataOut->pbData, pDataIn->pbData, pDataOut->cbData); 66 | return true; 67 | } 68 | 69 | typedef DWORD(WINAPI *pLogonUserW)( 70 | LPCWSTR lpszUsername, 71 | LPCWSTR lpszDomain, 72 | LPCWSTR lpszPassword, 73 | DWORD dwLogonType, 74 | DWORD dwLogonProvider, 75 | PHANDLE phToken); 76 | 77 | pLogonUserW RawLogonUserW = NULL; 78 | 79 | DWORD WINAPI MyLogonUserW( 80 | LPCWSTR lpszUsername, 81 | LPCWSTR lpszDomain, 82 | LPCWSTR lpszPassword, 83 | DWORD dwLogonType, 84 | DWORD dwLogonProvider, 85 | PHANDLE phToken) 86 | { 87 | DWORD ret = RawLogonUserW(lpszUsername, lpszDomain, lpszPassword, dwLogonType, dwLogonProvider, phToken); 88 | 89 | SetLastError(ERROR_ACCOUNT_RESTRICTION); 90 | return ret; 91 | } 92 | 93 | typedef BOOL(WINAPI *pIsOS)(DWORD dwOS); 94 | 95 | pIsOS RawIsOS = NULL; 96 | 97 | BOOL WINAPI MyIsOS( 98 | DWORD dwOS) 99 | { 100 | DWORD ret = RawIsOS(dwOS); 101 | if (dwOS == OS_DOMAINMEMBER) 102 | { 103 | return false; 104 | } 105 | 106 | return ret; 107 | } 108 | 109 | typedef NET_API_STATUS(WINAPI *pNetUserGetInfo)( 110 | LPCWSTR servername, 111 | LPCWSTR username, 112 | DWORD level, 113 | LPBYTE *bufptr); 114 | 115 | pNetUserGetInfo RawNetUserGetInfo = NULL; 116 | 117 | NET_API_STATUS WINAPI MyNetUserGetInfo( 118 | LPCWSTR servername, 119 | LPCWSTR username, 120 | DWORD level, 121 | LPBYTE *bufptr) 122 | { 123 | // DebugLog(L"MyNetUserGetInfo %s", username); 124 | 125 | NET_API_STATUS ret = RawNetUserGetInfo(servername, username, level, bufptr); 126 | if (level == 1 && ret == 0) 127 | { 128 | LPUSER_INFO_1 user_info = (LPUSER_INFO_1)*bufptr; 129 | // DebugLog(L"user_info %d %s", user_info->usri1_password_age, user_info->usri1_name); 130 | user_info->usri1_password_age = 0; 131 | // DebugLog(L"user_info %d", user_info->usri1_password_age); 132 | 133 | // DebugLog(L"User account name: %s\n", user_info->usri1_name); 134 | // DebugLog(L"Password: %s\n", user_info->usri1_password); 135 | // DebugLog(L"Password age (seconds): %d\n", user_info->usri1_password_age); 136 | // DebugLog(L"Privilege level: %d\n", user_info->usri1_priv); 137 | // DebugLog(L"Home directory: %s\n", user_info->usri1_home_dir); 138 | // DebugLog(L"User comment: %s\n", user_info->usri1_comment); 139 | // DebugLog(L"Flags (in hex): %x\n", user_info->usri1_flags); 140 | // DebugLog(L"Script path: %s\n", user_info->usri1_script_path); 141 | } 142 | 143 | return ret; 144 | } 145 | 146 | #define PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON (0x00000001ui64 << 44) 147 | #define PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON (0x00000001ui64 << 28) 148 | 149 | typedef BOOL(WINAPI *pUpdateProcThreadAttribute)( 150 | LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, 151 | DWORD dwFlags, 152 | DWORD_PTR Attribute, 153 | PVOID lpValue, 154 | SIZE_T cbSize, 155 | PVOID lpPreviousValue, 156 | PSIZE_T lpReturnSize); 157 | 158 | pUpdateProcThreadAttribute RawUpdateProcThreadAttribute = nullptr; 159 | 160 | BOOL WINAPI MyUpdateProcThreadAttribute( 161 | __inout LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, 162 | __in DWORD dwFlags, 163 | __in DWORD_PTR Attribute, 164 | __in_bcount_opt(cbSize) PVOID lpValue, 165 | __in SIZE_T cbSize, 166 | __out_bcount_opt(cbSize) PVOID lpPreviousValue, 167 | __in_opt PSIZE_T lpReturnSize) 168 | { 169 | if (Attribute == PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY && cbSize >= sizeof(DWORD64)) 170 | { 171 | // https://source.chromium.org/chromium/chromium/src/+/main:sandbox/win/src/process_mitigations.cc;l=362;drc=4c2fec5f6699ffeefd93137d2bf8c03504c6664c 172 | PDWORD64 policy_value_1 = &((PDWORD64)lpValue)[0]; 173 | *policy_value_1 &= ~PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON; 174 | *policy_value_1 &= ~PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON; 175 | } 176 | return RawUpdateProcThreadAttribute(lpAttributeList, dwFlags, Attribute, lpValue, cbSize, lpPreviousValue, lpReturnSize); 177 | } 178 | 179 | void MakeGreen() 180 | { 181 | HMODULE kernel32 = LoadLibraryW(L"kernel32.dll"); 182 | if (kernel32) 183 | { 184 | PBYTE GetComputerNameW = (PBYTE)GetProcAddress(kernel32, "GetComputerNameW"); 185 | PBYTE GetVolumeInformationW = (PBYTE)GetProcAddress(kernel32, "GetVolumeInformationW"); 186 | 187 | MH_STATUS status = MH_CreateHook(GetComputerNameW, FakeGetComputerName, NULL); 188 | if (status == MH_OK) 189 | { 190 | MH_EnableHook(GetComputerNameW); 191 | } 192 | else 193 | { 194 | DebugLog(L"MH_CreateHook GetComputerNameW failed:%d", status); 195 | } 196 | status = MH_CreateHook(GetVolumeInformationW, FakeGetVolumeInformation, NULL); 197 | if (status == MH_OK) 198 | { 199 | MH_EnableHook(GetVolumeInformationW); 200 | } 201 | else 202 | { 203 | DebugLog(L"MH_CreateHook GetVolumeInformationW failed:%d", status); 204 | } 205 | } 206 | 207 | // components/os_crypt/os_crypt_win.cc 208 | HMODULE Crypt32 = LoadLibraryW(L"Crypt32.dll"); 209 | if (Crypt32) 210 | { 211 | PBYTE CryptProtectData = (PBYTE)GetProcAddress(Crypt32, "CryptProtectData"); 212 | PBYTE CryptUnprotectData = (PBYTE)GetProcAddress(Crypt32, "CryptUnprotectData"); 213 | 214 | MH_STATUS status = MH_CreateHook(CryptProtectData, MyCryptProtectData, NULL); 215 | if (status == MH_OK) 216 | { 217 | MH_EnableHook(CryptProtectData); 218 | } 219 | else 220 | { 221 | DebugLog(L"MH_CreateHook CryptProtectData failed:%d", status); 222 | } 223 | status = MH_CreateHook(CryptUnprotectData, MyCryptUnprotectData, (LPVOID *)&RawCryptUnprotectData); 224 | if (status == MH_OK) 225 | { 226 | MH_EnableHook(CryptUnprotectData); 227 | } 228 | else 229 | { 230 | DebugLog(L"MH_CreateHook CryptUnprotectData failed:%d", status); 231 | } 232 | } 233 | 234 | HMODULE Advapi32 = LoadLibraryW(L"Advapi32.dll"); 235 | if (Advapi32) 236 | { 237 | PBYTE LogonUserW = (PBYTE)GetProcAddress(Advapi32, "LogonUserW"); 238 | 239 | MH_STATUS status = MH_CreateHook(LogonUserW, MyLogonUserW, (LPVOID *)&RawLogonUserW); 240 | if (status == MH_OK) 241 | { 242 | // DebugLog(L"MH_EnableHook LogonUserW"); 243 | MH_EnableHook(LogonUserW); 244 | } 245 | else 246 | { 247 | DebugLog(L"MH_CreateHook LogonUserW failed:%d", status); 248 | } 249 | } 250 | 251 | HMODULE Shlwapi = LoadLibraryW(L"Shlwapi.dll"); 252 | if (Shlwapi) 253 | { 254 | PBYTE IsOS = (PBYTE)GetProcAddress(Shlwapi, "IsOS"); 255 | 256 | MH_STATUS status = MH_CreateHook(IsOS, MyIsOS, (LPVOID *)&RawIsOS); 257 | if (status == MH_OK) 258 | { 259 | // DebugLog(L"MH_EnableHook IsOS"); 260 | MH_EnableHook(IsOS); 261 | } 262 | else 263 | { 264 | DebugLog(L"MH_CreateHook IsOS failed:%d", status); 265 | } 266 | } 267 | 268 | HMODULE Netapi32 = LoadLibraryW(L"Netapi32.dll"); 269 | if (Netapi32) 270 | { 271 | PBYTE NetUserGetInfo = (PBYTE)GetProcAddress(Netapi32, "NetUserGetInfo"); 272 | 273 | MH_STATUS status = MH_CreateHook(NetUserGetInfo, MyNetUserGetInfo, (LPVOID *)&RawNetUserGetInfo); 274 | if (status == MH_OK) 275 | { 276 | MH_EnableHook(NetUserGetInfo); 277 | } 278 | else 279 | { 280 | DebugLog(L"MH_CreateHook NetUserGetInfo failed:%d", status); 281 | } 282 | } 283 | 284 | LPVOID ppUpdateProcThreadAttribute = nullptr; 285 | MH_STATUS status = MH_CreateHookApiEx(L"kernel32", "UpdateProcThreadAttribute", 286 | &MyUpdateProcThreadAttribute, (LPVOID *)&RawUpdateProcThreadAttribute, &ppUpdateProcThreadAttribute); 287 | if (status == MH_OK) 288 | { 289 | MH_EnableHook(ppUpdateProcThreadAttribute); 290 | } 291 | else 292 | { 293 | DebugLog(L"MH_CreateHookApiEx UpdateProcThreadAttribute failed: %d", status); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/hijack.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace hijack 6 | { 7 | 8 | #define NOP_FUNC \ 9 | { \ 10 | __nop(); \ 11 | __nop(); \ 12 | __nop(); \ 13 | __nop(); \ 14 | __nop(); \ 15 | __nop(); \ 16 | __nop(); \ 17 | __nop(); \ 18 | __nop(); \ 19 | __nop(); \ 20 | __nop(); \ 21 | __nop(); \ 22 | return __COUNTER__; \ 23 | } 24 | // 用 __COUNTER__ 来生成一点不一样的代码,避免被 VS 自动合并相同函数 25 | 26 | #define EXPORT(api) int __cdecl api() NOP_FUNC 27 | 28 | #pragma region 声明导出函数 29 | // 声明导出函数 30 | #pragma comment(linker, "/export:GetFileVersionInfoA=?GetFileVersionInfoA@hijack@@YAHXZ,@1") 31 | #pragma comment(linker, "/export:GetFileVersionInfoByHandle=?GetFileVersionInfoByHandle@hijack@@YAHXZ,@2") 32 | #pragma comment(linker, "/export:GetFileVersionInfoExA=?GetFileVersionInfoExA@hijack@@YAHXZ,@3") 33 | #pragma comment(linker, "/export:GetFileVersionInfoExW=?GetFileVersionInfoExW@hijack@@YAHXZ,@4") 34 | #pragma comment(linker, "/export:GetFileVersionInfoSizeA=?GetFileVersionInfoSizeA@hijack@@YAHXZ,@5") 35 | #pragma comment(linker, "/export:GetFileVersionInfoSizeExA=?GetFileVersionInfoSizeExA@hijack@@YAHXZ,@6") 36 | #pragma comment(linker, "/export:GetFileVersionInfoSizeExW=?GetFileVersionInfoSizeExW@hijack@@YAHXZ,@7") 37 | #pragma comment(linker, "/export:GetFileVersionInfoSizeW=?GetFileVersionInfoSizeW@hijack@@YAHXZ,@8") 38 | #pragma comment(linker, "/export:GetFileVersionInfoW=?GetFileVersionInfoW@hijack@@YAHXZ,@9") 39 | #pragma comment(linker, "/export:VerFindFileA=?VerFindFileA@hijack@@YAHXZ,@10") 40 | #pragma comment(linker, "/export:VerFindFileW=?VerFindFileW@hijack@@YAHXZ,@11") 41 | #pragma comment(linker, "/export:VerInstallFileA=?VerInstallFileA@hijack@@YAHXZ,@12") 42 | #pragma comment(linker, "/export:VerInstallFileW=?VerInstallFileW@hijack@@YAHXZ,@13") 43 | #pragma comment(linker, "/export:VerLanguageNameA=?VerLanguageNameA@hijack@@YAHXZ,@14") 44 | #pragma comment(linker, "/export:VerLanguageNameW=?VerLanguageNameW@hijack@@YAHXZ,@15") 45 | #pragma comment(linker, "/export:VerQueryValueA=?VerQueryValueA@hijack@@YAHXZ,@16") 46 | #pragma comment(linker, "/export:VerQueryValueW=?VerQueryValueW@hijack@@YAHXZ,@17") 47 | 48 | EXPORT(GetFileVersionInfoA) 49 | EXPORT(GetFileVersionInfoByHandle) 50 | EXPORT(GetFileVersionInfoExA) 51 | EXPORT(GetFileVersionInfoExW) 52 | EXPORT(GetFileVersionInfoSizeA) 53 | EXPORT(GetFileVersionInfoSizeExA) 54 | EXPORT(GetFileVersionInfoSizeExW) 55 | EXPORT(GetFileVersionInfoSizeW) 56 | EXPORT(GetFileVersionInfoW) 57 | EXPORT(VerFindFileA) 58 | EXPORT(VerFindFileW) 59 | EXPORT(VerInstallFileA) 60 | EXPORT(VerInstallFileW) 61 | EXPORT(VerLanguageNameA) 62 | EXPORT(VerLanguageNameW) 63 | EXPORT(VerQueryValueA) 64 | EXPORT(VerQueryValueW) 65 | } // namespace hijack 66 | #pragma endregion 67 | 68 | #pragma region 还原导出函数 69 | bool WriteMemory(PBYTE BaseAddress, PBYTE Buffer, DWORD nSize) 70 | { 71 | DWORD ProtectFlag = 0; 72 | if (VirtualProtectEx(GetCurrentProcess(), BaseAddress, nSize, PAGE_EXECUTE_READWRITE, &ProtectFlag)) 73 | { 74 | memcpy(BaseAddress, Buffer, nSize); 75 | FlushInstructionCache(GetCurrentProcess(), BaseAddress, nSize); 76 | VirtualProtectEx(GetCurrentProcess(), BaseAddress, nSize, ProtectFlag, &ProtectFlag); 77 | return true; 78 | } 79 | return false; 80 | } 81 | 82 | // 还原导出函数 83 | void InstallJMP(PBYTE BaseAddress, uintptr_t Function) 84 | { 85 | if (*BaseAddress == 0xE9) 86 | { 87 | BaseAddress++; 88 | BaseAddress = BaseAddress + *(uint32_t *)BaseAddress + 4; 89 | } 90 | #ifdef _WIN64 91 | BYTE move[] = {0x48, 0xB8}; // move rax,xxL); 92 | BYTE jump[] = {0xFF, 0xE0}; // jmp rax 93 | 94 | WriteMemory(BaseAddress, move, sizeof(move)); 95 | BaseAddress += sizeof(move); 96 | 97 | WriteMemory(BaseAddress, (PBYTE)&Function, sizeof(uintptr_t)); 98 | BaseAddress += sizeof(uintptr_t); 99 | 100 | WriteMemory(BaseAddress, jump, sizeof(jump)); 101 | #else 102 | BYTE jump[] = {0xE9}; 103 | WriteMemory(BaseAddress, jump, sizeof(jump)); 104 | BaseAddress += sizeof(jump); 105 | 106 | uintptr_t offset = Function - (uintptr_t)BaseAddress - 4; 107 | WriteMemory(BaseAddress, (PBYTE)&offset, sizeof(offset)); 108 | #endif // _WIN64 109 | } 110 | #pragma endregion 111 | 112 | #pragma region 加载系统dll 113 | void LoadVersion(HINSTANCE hModule) 114 | { 115 | PBYTE pImageBase = (PBYTE)hModule; 116 | PIMAGE_DOS_HEADER pimDH = (PIMAGE_DOS_HEADER)pImageBase; 117 | if (pimDH->e_magic == IMAGE_DOS_SIGNATURE) 118 | { 119 | PIMAGE_NT_HEADERS pimNH = (PIMAGE_NT_HEADERS)(pImageBase + pimDH->e_lfanew); 120 | if (pimNH->Signature == IMAGE_NT_SIGNATURE) 121 | { 122 | PIMAGE_EXPORT_DIRECTORY pimExD = (PIMAGE_EXPORT_DIRECTORY)(pImageBase + pimNH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); 123 | DWORD *pName = (DWORD *)(pImageBase + pimExD->AddressOfNames); 124 | DWORD *pFunction = (DWORD *)(pImageBase + pimExD->AddressOfFunctions); 125 | WORD *pNameOrdinals = (WORD *)(pImageBase + pimExD->AddressOfNameOrdinals); 126 | 127 | wchar_t szSysDirectory[MAX_PATH + 1]; 128 | GetSystemDirectory(szSysDirectory, MAX_PATH); 129 | 130 | wchar_t szDLLPath[MAX_PATH + 1]; 131 | lstrcpy(szDLLPath, szSysDirectory); 132 | lstrcat(szDLLPath, TEXT("\\version.dll")); 133 | 134 | HINSTANCE module = LoadLibrary(szDLLPath); 135 | for (size_t i = 0; i < pimExD->NumberOfNames; i++) 136 | { 137 | uintptr_t Original = (uintptr_t)GetProcAddress(module, (char *)(pImageBase + pName[i])); 138 | if (Original) 139 | { 140 | InstallJMP(pImageBase + pFunction[pNameOrdinals[i]], Original); 141 | } 142 | } 143 | } 144 | } 145 | } 146 | #pragma endregion 147 | 148 | void LoadSysDll(HINSTANCE hModule) 149 | { 150 | LoadVersion(hModule); 151 | } 152 | -------------------------------------------------------------------------------- /src/patch.h: -------------------------------------------------------------------------------- 1 | typedef LONG NTSTATUS, *PNTSTATUS; 2 | 3 | #ifndef NT_SUCCESS 4 | #define NT_SUCCESS(x) ((x) >= 0) 5 | #define STATUS_SUCCESS ((NTSTATUS)0) 6 | #endif 7 | 8 | typedef struct _UNICODE_STRING 9 | { 10 | USHORT Length; 11 | USHORT MaximumLength; 12 | PWSTR Buffer; 13 | } UNICODE_STRING, *PUNICODE_STRING; 14 | 15 | typedef NTSTATUS(WINAPI *pLdrLoadDll)(IN PWCHAR PathToFile OPTIONAL, IN ULONG Flags OPTIONAL, 16 | IN PUNICODE_STRING ModuleFileName, OUT PHANDLE ModuleHandle); 17 | 18 | pLdrLoadDll RawLdrLoadDll = nullptr; 19 | 20 | void Outdated(HMODULE module) 21 | { 22 | // "OutdatedUpgradeBubble.Show" 23 | #ifdef _WIN64 24 | BYTE search[] = {0x48, 0x89, 0x8C, 0x24, 0xF0, 0x00, 0x00, 0x00, 0x80, 0x3D}; 25 | uint8_t *match = SearchModuleRaw(module, search, sizeof(search)); 26 | #else 27 | BYTE search[] = {0x31, 0xE8, 0x89, 0x45, 0xF0, 0x88, 0x5D, 0xEF, 0x80, 0x3D}; 28 | uint8_t *match = SearchModuleRaw(module, search, sizeof(search)); 29 | #endif 30 | if (match) 31 | { 32 | if (*(match + 0xF) == 0x74) 33 | { 34 | BYTE patch[] = {0x90, 0x90}; 35 | WriteMemory(match + 0xF, patch, sizeof(patch)); 36 | } 37 | } 38 | else 39 | { 40 | DebugLog(L"patch Outdated failed %p", module); 41 | } 42 | } 43 | 44 | void DevWarning(HMODULE module) 45 | { 46 | // "enable-automation" 47 | } 48 | 49 | NTSTATUS WINAPI MyLdrLoadDll(IN PWCHAR PathToFile OPTIONAL, 50 | IN ULONG Flags OPTIONAL, 51 | IN PUNICODE_STRING ModuleFileName, 52 | OUT PHANDLE ModuleHandle) 53 | { 54 | static bool vivaldi_loaded = false; 55 | 56 | NTSTATUS ntstatus = RawLdrLoadDll(PathToFile, Flags, ModuleFileName, ModuleHandle); 57 | if (NT_SUCCESS(ntstatus)) 58 | { 59 | if (wcsstr(ModuleFileName->Buffer, L"vivaldi.dll") != 0 && !vivaldi_loaded) 60 | { 61 | vivaldi_loaded = true; 62 | Outdated((HMODULE)*ModuleHandle); 63 | DevWarning((HMODULE)*ModuleHandle); 64 | } 65 | } 66 | return ntstatus; 67 | }; 68 | 69 | void MakePatch() 70 | { 71 | // HMODULE chrome = GetModuleHandle(L"chrome.dll"); 72 | // if (chrome) 73 | // { 74 | // Outdated(chrome); 75 | // DevWarning(chrome); 76 | // return; 77 | // } 78 | HMODULE ntdll = GetModuleHandle(L"ntdll.dll"); 79 | if (ntdll) 80 | { 81 | PBYTE LdrLoadDll = (PBYTE)GetProcAddress(ntdll, "LdrLoadDll"); 82 | MH_STATUS status = MH_CreateHook(LdrLoadDll, MyLdrLoadDll, (LPVOID *)&RawLdrLoadDll); 83 | if (status == MH_OK) 84 | { 85 | MH_EnableHook(LdrLoadDll); 86 | } 87 | else 88 | { 89 | DebugLog(L"MH_CreateHook LdrLoadDll failed:%d", status); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/portable.h: -------------------------------------------------------------------------------- 1 | std::wstring QuoteSpaceIfNeeded(const std::wstring &str) 2 | { 3 | if (str.find(L' ') == std::wstring::npos) 4 | return std::move(str); 5 | 6 | std::wstring escaped(L"\""); 7 | for (auto c : str) 8 | { 9 | if (c == L'"') 10 | escaped += L'"'; 11 | escaped += c; 12 | } 13 | escaped += L'"'; 14 | return std::move(escaped); 15 | } 16 | 17 | std::wstring JoinArgsString(std::vector lines, const std::wstring &delimiter) 18 | { 19 | std::wstring text; 20 | bool first = true; 21 | for (auto &line : lines) 22 | { 23 | if (!first) 24 | text += delimiter; 25 | else 26 | first = false; 27 | text += QuoteSpaceIfNeeded(line); 28 | } 29 | return text; 30 | } 31 | 32 | bool IsExistsPortable() 33 | { 34 | std::wstring path = GetAppDir() + L"\\portable"; 35 | if (PathFileExists(path.data())) 36 | { 37 | return true; 38 | } 39 | return false; 40 | } 41 | 42 | bool IsNeedPortable() 43 | { 44 | return true; 45 | // static bool need_portable = IsExistsPortable(); 46 | // return need_portable; 47 | } 48 | 49 | bool IsCustomIniExist() 50 | { 51 | std::wstring path = GetAppDir() + L"\\config.ini"; 52 | if (PathFileExists(path.data())) 53 | { 54 | return true; 55 | } 56 | return false; 57 | } 58 | 59 | // GetUserDataDir retrieves the user data directory path from the config file. 60 | // It first tries to read the data dir from the "data" key in the "dir_setting" section. 61 | // If that fails, it falls back to a default relative to the app dir. 62 | // It expands any environment variables in the path. 63 | std::wstring GetUserDataDir() 64 | { 65 | std::wstring configFilePath = GetAppDir() + L"\\config.ini"; 66 | if (!PathFileExists(configFilePath.c_str())) 67 | { 68 | return GetAppDir() + L"\\..\\Data"; 69 | } 70 | 71 | TCHAR userDataBuffer[MAX_PATH]; 72 | ::GetPrivateProfileStringW(L"dir_setting", L"data", L"", userDataBuffer, MAX_PATH, configFilePath.c_str()); 73 | 74 | std::wstring expandedPath = ExpandEnvironmentPath(userDataBuffer); 75 | 76 | // Expand %app% 77 | ReplaceStringInPlace(expandedPath, L"%app%", GetAppDir()); 78 | 79 | std::wstring dataDir; 80 | dataDir = GetAbsolutePath(expandedPath); 81 | 82 | wcscpy(userDataBuffer, dataDir.c_str()); 83 | 84 | return std::wstring(userDataBuffer); 85 | } 86 | 87 | // GetDiskCacheDir retrieves the disk cache directory path from the config file. 88 | // It first tries to read the cache dir from the "cache" key in the "dir_setting" section. 89 | // If that fails, it falls back to a default relative to the app dir. 90 | // It expands any environment variables in the path. 91 | std::wstring GetDiskCacheDir() 92 | { 93 | std::wstring configFilePath = GetAppDir() + L"\\config.ini"; 94 | 95 | if (!PathFileExists(configFilePath.c_str())) 96 | { 97 | return GetAppDir() + L"\\..\\Cache"; 98 | } 99 | 100 | TCHAR cacheDirBuffer[MAX_PATH]; 101 | ::GetPrivateProfileStringW(L"dir_setting", L"cache", L"", cacheDirBuffer, MAX_PATH, configFilePath.c_str()); 102 | 103 | std::wstring expandedPath = ExpandEnvironmentPath(cacheDirBuffer); 104 | 105 | // Expand %app% 106 | ReplaceStringInPlace(expandedPath, L"%app%", GetAppDir()); 107 | 108 | std::wstring cacheDir; 109 | cacheDir = GetAbsolutePath(expandedPath); 110 | wcscpy(cacheDirBuffer, cacheDir.c_str()); 111 | 112 | return std::wstring(cacheDirBuffer); 113 | } 114 | 115 | // 构造新命令行 116 | // Parses the command line param into individual arguments and inserts 117 | // additional arguments for portable mode. 118 | // 119 | // param: The command line passed to the application. 120 | // 121 | // Returns: The modified command line with additional args. 122 | std::wstring GetCommand(LPWSTR param) 123 | { 124 | std::vector args; 125 | 126 | int argc; 127 | LPWSTR *argv = CommandLineToArgvW(param, &argc); 128 | 129 | int insert_pos = 0; 130 | for (int i = 0; i < argc; i++) 131 | { 132 | if (wcscmp(argv[i], L"--") == 0) 133 | { 134 | break; 135 | } 136 | if (wcscmp(argv[i], L"--single-argument") == 0) 137 | { 138 | break; 139 | } 140 | insert_pos = i; 141 | } 142 | for (int i = 0; i < argc; i++) 143 | { 144 | // 保留原来参数 145 | if (i) 146 | args.push_back(argv[i]); 147 | 148 | // 追加参数 149 | if (i == insert_pos) 150 | { 151 | args.push_back(L"--gopher"); 152 | 153 | // args.push_back(L"--force-local-ntp"); 154 | // args.push_back(L"--disable-background-networking"); 155 | 156 | args.push_back(L"--disable-features=RendererCodeIntegrity,FlashDeprecationWarning"); 157 | 158 | // if (IsNeedPortable()) 159 | { 160 | auto diskcache = GetDiskCacheDir(); 161 | 162 | wchar_t temp[MAX_PATH]; 163 | wsprintf(temp, L"--disk-cache-dir=%s", diskcache.c_str()); 164 | args.push_back(temp); 165 | } 166 | { 167 | auto userdata = GetUserDataDir(); 168 | 169 | wchar_t temp[MAX_PATH]; 170 | wsprintf(temp, L"--user-data-dir=%s", userdata.c_str()); 171 | args.push_back(temp); 172 | } 173 | } 174 | } 175 | LocalFree(argv); 176 | 177 | return JoinArgsString(args, L" "); 178 | } 179 | 180 | void Portable(LPWSTR param) 181 | { 182 | wchar_t path[MAX_PATH]; 183 | ::GetModuleFileName(NULL, path, MAX_PATH); 184 | 185 | std::wstring args = GetCommand(param); 186 | 187 | SHELLEXECUTEINFO sei = {0}; 188 | sei.cbSize = sizeof(SHELLEXECUTEINFO); 189 | sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI; 190 | sei.lpVerb = L"open"; 191 | sei.lpFile = path; 192 | sei.nShow = SW_SHOWNORMAL; 193 | 194 | sei.lpParameters = args.c_str(); 195 | if (ShellExecuteEx(&sei)) 196 | { 197 | ExitProcess(0); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "FastSearch.h" 9 | 10 | std::wstring Format(const wchar_t *format, va_list args) 11 | { 12 | std::vector buffer; 13 | 14 | size_t length = _vscwprintf(format, args); 15 | 16 | buffer.resize((length + 1) * sizeof(wchar_t)); 17 | 18 | _vsnwprintf_s(&buffer[0], length + 1, length, format, args); 19 | 20 | return std::wstring(&buffer[0]); 21 | } 22 | 23 | std::wstring Format(const wchar_t *format, ...) 24 | { 25 | va_list args; 26 | 27 | va_start(args, format); 28 | auto str = Format(format, args); 29 | va_end(args); 30 | 31 | return str; 32 | } 33 | 34 | void DebugLog(const wchar_t *format, ...) 35 | { 36 | va_list args; 37 | 38 | va_start(args, format); 39 | auto str = Format(format, args); 40 | va_end(args); 41 | 42 | str = Format(L"[vivaldi++]%s\n", str.c_str()); 43 | 44 | OutputDebugStringW(str.c_str()); 45 | } 46 | 47 | // 搜索内存 48 | uint8_t *memmem(uint8_t *src, int n, const uint8_t *sub, int m) 49 | { 50 | return (uint8_t *)FastSearch(src, n, sub, m); 51 | } 52 | 53 | uint8_t *SearchModuleRaw(HMODULE module, const uint8_t *sub, int m) 54 | { 55 | uint8_t *buffer = (uint8_t *)module; 56 | 57 | PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)(buffer + ((PIMAGE_DOS_HEADER)buffer)->e_lfanew); 58 | PIMAGE_SECTION_HEADER section = (PIMAGE_SECTION_HEADER)((char *)nt_header + sizeof(DWORD) + 59 | sizeof(IMAGE_FILE_HEADER) + nt_header->FileHeader.SizeOfOptionalHeader); 60 | 61 | for (int i = 0; i < nt_header->FileHeader.NumberOfSections; i++) 62 | { 63 | if (strcmp((const char *)section[i].Name, ".text") == 0) 64 | { 65 | return memmem(buffer + section[i].PointerToRawData, section[i].SizeOfRawData, sub, m); 66 | break; 67 | } 68 | } 69 | return NULL; 70 | } 71 | 72 | uint8_t *SearchModuleRaw2(HMODULE module, const uint8_t *sub, int m) 73 | { 74 | uint8_t *buffer = (uint8_t *)module; 75 | 76 | PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)(buffer + ((PIMAGE_DOS_HEADER)buffer)->e_lfanew); 77 | PIMAGE_SECTION_HEADER section = (PIMAGE_SECTION_HEADER)((char *)nt_header + sizeof(DWORD) + 78 | sizeof(IMAGE_FILE_HEADER) + nt_header->FileHeader.SizeOfOptionalHeader); 79 | 80 | for (int i = 0; i < nt_header->FileHeader.NumberOfSections; i++) 81 | { 82 | if (strcmp((const char *)section[i].Name, ".rdata") == 0) 83 | { 84 | return memmem(buffer + section[i].PointerToRawData, section[i].SizeOfRawData, sub, m); 85 | break; 86 | } 87 | } 88 | return NULL; 89 | } 90 | #include 91 | #pragma comment(lib, "Shlwapi.lib") 92 | 93 | // 获得程序所在文件夹 94 | std::wstring GetAppDir() 95 | { 96 | wchar_t path[MAX_PATH]; 97 | ::GetModuleFileName(NULL, path, MAX_PATH); 98 | ::PathRemoveFileSpec(path); 99 | return path; 100 | } 101 | 102 | #define IDC_NEW_TAB 34014 103 | #define IDC_CLOSE_TAB 34015 104 | #define IDC_SELECT_NEXT_TAB 34016 105 | #define IDC_SELECT_PREVIOUS_TAB 34017 106 | #define IDC_SELECT_TAB_0 34018 107 | #define IDC_SELECT_TAB_1 34019 108 | #define IDC_SELECT_TAB_2 34020 109 | #define IDC_SELECT_TAB_3 34021 110 | #define IDC_SELECT_TAB_4 34022 111 | #define IDC_SELECT_TAB_5 34023 112 | #define IDC_SELECT_TAB_6 34024 113 | #define IDC_SELECT_TAB_7 34025 114 | #define IDC_SELECT_LAST_TAB 34026 115 | 116 | #define IDC_UPGRADE_DIALOG 40024 117 | 118 | HWND GetTopWnd(HWND hwnd) 119 | { 120 | while (::GetParent(hwnd) && ::IsWindowVisible(::GetParent(hwnd))) 121 | { 122 | hwnd = ::GetParent(hwnd); 123 | } 124 | return hwnd; 125 | } 126 | 127 | void ExecuteCommand(int id, HWND hwnd = 0) 128 | { 129 | if (hwnd == 0) 130 | hwnd = GetForegroundWindow(); 131 | // hwnd = GetTopWnd(hwnd); 132 | // hwnd = GetForegroundWindow(); 133 | // PostMessage(hwnd, WM_SYSCOMMAND, id, 0); 134 | ::SendMessageTimeoutW(hwnd, WM_SYSCOMMAND, id, 0, 0, 1000, 0); 135 | } 136 | 137 | // 发送按键 138 | template 139 | void SendKey(T... keys) 140 | { 141 | std::vector inputs; 142 | std::vector keys_ = {keys...}; 143 | for (auto &key : keys_) 144 | { 145 | INPUT input = {0}; 146 | input.type = INPUT_KEYBOARD; 147 | input.ki.dwFlags = KEYEVENTF_EXTENDEDKEY; 148 | input.ki.wVk = (WORD)key; 149 | input.ki.dwExtraInfo = MAGIC_CODE; 150 | 151 | // 修正鼠标消息 152 | switch (input.ki.wVk) 153 | { 154 | case VK_RBUTTON: 155 | input.type = INPUT_MOUSE; 156 | input.mi.dwFlags = ::GetSystemMetrics(SM_SWAPBUTTON) == TRUE ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_RIGHTDOWN; 157 | input.mi.dwExtraInfo = MAGIC_CODE; 158 | break; 159 | case VK_LBUTTON: 160 | input.type = INPUT_MOUSE; 161 | input.mi.dwFlags = ::GetSystemMetrics(SM_SWAPBUTTON) == TRUE ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_LEFTDOWN; 162 | input.mi.dwExtraInfo = MAGIC_CODE; 163 | break; 164 | case VK_MBUTTON: 165 | input.type = INPUT_MOUSE; 166 | input.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN; 167 | input.mi.dwExtraInfo = MAGIC_CODE; 168 | break; 169 | } 170 | 171 | inputs.push_back(input); 172 | } 173 | std::reverse(keys_.begin(), keys_.end()); 174 | for (auto &key : keys_) 175 | { 176 | INPUT input = {0}; 177 | input.type = INPUT_KEYBOARD; 178 | input.ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP; 179 | input.ki.wVk = (WORD)key; 180 | input.ki.dwExtraInfo = MAGIC_CODE; 181 | 182 | // 修正鼠标消息 183 | switch (input.ki.wVk) 184 | { 185 | case VK_RBUTTON: 186 | input.type = INPUT_MOUSE; 187 | input.mi.dwFlags = ::GetSystemMetrics(SM_SWAPBUTTON) == TRUE ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_RIGHTUP; 188 | input.mi.dwExtraInfo = MAGIC_CODE; 189 | break; 190 | case VK_LBUTTON: 191 | input.type = INPUT_MOUSE; 192 | input.mi.dwFlags = ::GetSystemMetrics(SM_SWAPBUTTON) == TRUE ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_LEFTUP; 193 | input.mi.dwExtraInfo = MAGIC_CODE; 194 | break; 195 | case VK_MBUTTON: 196 | input.type = INPUT_MOUSE; 197 | input.mi.dwFlags = MOUSEEVENTF_MIDDLEUP; 198 | input.mi.dwExtraInfo = MAGIC_CODE; 199 | break; 200 | } 201 | 202 | inputs.push_back(input); 203 | } 204 | // for (auto & key : inputs) 205 | //{ 206 | // DebugLog(L"%X %X", key.ki.wVk, key.mi.dwFlags); 207 | // } 208 | 209 | ::SendInput((UINT)inputs.size(), &inputs[0], sizeof(INPUT)); 210 | } 211 | 212 | // 发送鼠标消息 213 | void SendOneMouse(int mouse) 214 | { 215 | // 交换左右键 216 | if (::GetSystemMetrics(SM_SWAPBUTTON) == TRUE) 217 | { 218 | if (mouse == MOUSEEVENTF_RIGHTDOWN) 219 | mouse = MOUSEEVENTF_LEFTDOWN; 220 | else if (mouse == MOUSEEVENTF_RIGHTUP) 221 | mouse = MOUSEEVENTF_LEFTUP; 222 | } 223 | 224 | INPUT input[1]; 225 | memset(input, 0, sizeof(input)); 226 | 227 | input[0].type = INPUT_MOUSE; 228 | 229 | input[0].mi.dwFlags = mouse; 230 | input[0].mi.dwExtraInfo = MAGIC_CODE; 231 | ::SendInput(1, input, sizeof(INPUT)); 232 | } 233 | 234 | bool isEndWith(const wchar_t *s, const wchar_t *sub) 235 | { 236 | if (!s || !sub) 237 | return false; 238 | size_t len1 = wcslen(s); 239 | size_t len2 = wcslen(sub); 240 | if (len2 > len1) 241 | return false; 242 | return !_memicmp(s + len1 - len2, sub, len2 * sizeof(wchar_t)); 243 | } 244 | 245 | // 获得指定路径的绝对路径,如 "data/../Cache" 246 | std::wstring GetAbsolutePath(const std::wstring &path) 247 | { 248 | wchar_t buffer[MAX_PATH]; 249 | ::GetFullPathNameW(path.c_str(), MAX_PATH, buffer, NULL); 250 | return buffer; 251 | } 252 | 253 | // 展开环境路径比如 %windir% 254 | std::wstring ExpandEnvironmentPath(const std::wstring &path) 255 | { 256 | std::vector buffer(MAX_PATH); 257 | size_t expandedLength = ::ExpandEnvironmentStrings(path.c_str(), &buffer[0], (DWORD)buffer.size()); 258 | if (expandedLength > buffer.size()) 259 | { 260 | buffer.resize(expandedLength); 261 | expandedLength = ::ExpandEnvironmentStrings(path.c_str(), &buffer[0], (DWORD)buffer.size()); 262 | } 263 | return std::wstring(&buffer[0], 0, expandedLength); 264 | } 265 | // 替换字符串 266 | void ReplaceStringInPlace(std::wstring &subject, const std::wstring &search, const std::wstring &replace) 267 | { 268 | size_t pos = 0; 269 | while ((pos = subject.find(search, pos)) != std::wstring::npos) 270 | { 271 | subject.replace(pos, search.length(), replace); 272 | pos += replace.length(); 273 | } 274 | } 275 | 276 | // 压缩HTML 277 | std::string <rim(std::string &s) 278 | { 279 | s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); })); 280 | return s; 281 | } 282 | std::string &rtrim(std::string &s) 283 | { 284 | s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); 285 | return s; 286 | } 287 | 288 | std::string &trim(std::string &s) 289 | { 290 | return ltrim(rtrim(s)); 291 | } 292 | 293 | std::vector split(const std::string &text, char sep) 294 | { 295 | std::vector tokens; 296 | std::size_t start = 0, end = 0; 297 | while ((end = text.find(sep, start)) != std::string::npos) 298 | { 299 | std::string temp = text.substr(start, end - start); 300 | tokens.push_back(temp); 301 | start = end + 1; 302 | } 303 | std::string temp = text.substr(start); 304 | tokens.push_back(temp); 305 | return tokens; 306 | } 307 | 308 | void compression_html(std::string &html) 309 | { 310 | auto lines = split(html, '\n'); 311 | html.clear(); 312 | for (auto &line : lines) 313 | { 314 | html += "\n"; 315 | html += trim(line); 316 | } 317 | } 318 | 319 | // 替换字符串 320 | bool ReplaceStringInPlace(std::string &subject, const std::string &search, const std::string &replace) 321 | { 322 | bool find = false; 323 | size_t pos = 0; 324 | while ((pos = subject.find(search, pos)) != std::string::npos) 325 | { 326 | subject.replace(pos, search.length(), replace); 327 | pos += replace.length(); 328 | find = true; 329 | } 330 | return find; 331 | } 332 | // bool WriteMemory(PBYTE BaseAddress, PBYTE Buffer, DWORD nSize) 333 | //{ 334 | // DWORD ProtectFlag = 0; 335 | // if (VirtualProtectEx(GetCurrentProcess(), BaseAddress, nSize, PAGE_EXECUTE_READWRITE, &ProtectFlag)) 336 | // { 337 | // memcpy(BaseAddress, Buffer, nSize); 338 | // FlushInstructionCache(GetCurrentProcess(), BaseAddress, nSize); 339 | // VirtualProtectEx(GetCurrentProcess(), BaseAddress, nSize, ProtectFlag, &ProtectFlag); 340 | // return true; 341 | // } 342 | // return false; 343 | // } 344 | -------------------------------------------------------------------------------- /src/version.h: -------------------------------------------------------------------------------- 1 | #define RELEASE_VER_MAIN 1 2 | #define RELEASE_VER_SUB 5 3 | #define RELEASE_VER_FIX 8 4 | #define RELEASE_VER_PATCH 2 5 | 6 | #define TOSTRING2(arg) #arg 7 | #define TOSTRING(arg) TOSTRING2(arg) 8 | 9 | #define RELEASE_VER_STR TOSTRING(RELEASE_VER_MAIN) "." TOSTRING(RELEASE_VER_SUB) "." TOSTRING(RELEASE_VER_FIX) "." TOSTRING(RELEASE_VER_PATCH) 10 | -------------------------------------------------------------------------------- /src/vivaldi++.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | HMODULE hInstance; 6 | 7 | #define MAGIC_CODE 0x1603ABD9 8 | 9 | #include "MinHook.h" 10 | #include "version.h" 11 | 12 | #include "hijack.h" 13 | #include "utils.h" 14 | #include "patch.h" 15 | #include "featuresFlag.h" 16 | #include "TabBookmark.h" 17 | #include "portable.h" 18 | #include "PakPatch.h" 19 | #include "appid.h" 20 | #include "green.h" 21 | 22 | typedef int (*Startup)(); 23 | Startup ExeMain = NULL; 24 | 25 | void VivaldiPlus() 26 | { 27 | // 快捷方式 28 | SetAppId(); 29 | 30 | // 读取特性标志 31 | ParseFeatureFlags(); 32 | 33 | // 便携化补丁 34 | MakeGreen(); 35 | 36 | // 标签页,书签,地址栏增强 37 | TabBookmark(); 38 | 39 | // 给pak文件打补丁 40 | PakPatch(); 41 | } 42 | 43 | void VivaldiPlusCommand(LPWSTR param) 44 | { 45 | if (!wcsstr(param, L"--gopher")) 46 | { 47 | Portable(param); 48 | } 49 | else 50 | { 51 | VivaldiPlus(); 52 | } 53 | } 54 | 55 | int Loader() 56 | { 57 | // 硬补丁 58 | MakePatch(); 59 | 60 | // 只关注主界面 61 | LPWSTR param = GetCommandLineW(); 62 | // DebugLog(L"param %s", param); 63 | if (!wcsstr(param, L"-type=")) 64 | { 65 | VivaldiPlusCommand(param); 66 | } 67 | 68 | // 返回到主程序 69 | return ExeMain(); 70 | } 71 | 72 | void InstallLoader() 73 | { 74 | // 获取程序入口点 75 | MODULEINFO mi; 76 | GetModuleInformation(GetCurrentProcess(), GetModuleHandle(NULL), &mi, sizeof(MODULEINFO)); 77 | PBYTE entry = (PBYTE)mi.EntryPoint; 78 | 79 | // 入口点跳转到Loader 80 | MH_STATUS status = MH_CreateHook(entry, Loader, (LPVOID *)&ExeMain); 81 | if (status == MH_OK) 82 | { 83 | MH_EnableHook(entry); 84 | } 85 | else 86 | { 87 | DebugLog(L"MH_CreateHook InstallLoader failed:%d", status); 88 | } 89 | } 90 | #define EXTERNC extern "C" 91 | 92 | // 93 | EXTERNC __declspec(dllexport) void gopher() 94 | { 95 | } 96 | 97 | EXTERNC BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID pv) 98 | { 99 | if (dwReason == DLL_PROCESS_ATTACH) 100 | { 101 | DisableThreadLibraryCalls(hModule); 102 | hInstance = hModule; 103 | 104 | // 保持系统dll原有功能 105 | LoadSysDll(hModule); 106 | 107 | // 初始化HOOK库成功以后安装加载器 108 | MH_STATUS status = MH_Initialize(); 109 | if (status == MH_OK) 110 | { 111 | InstallLoader(); 112 | } 113 | else 114 | { 115 | DebugLog(L"MH_Initialize failed:%d", status); 116 | } 117 | } 118 | return TRUE; 119 | } 120 | -------------------------------------------------------------------------------- /src/vivaldi++.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ca-x/vivaldi_plus/a7f3030becc459ddc1eea26430475286a3a2ad53/src/vivaldi++.rc -------------------------------------------------------------------------------- /sweep.yaml: -------------------------------------------------------------------------------- 1 | # Sweep AI turns bugs & feature requests into code changes (https://sweep.dev) 2 | # For details on our config file, check out our docs at https://docs.sweep.dev/usage/config 3 | 4 | # This setting contains a list of rules that Sweep will check for. If any of these rules are broken in a new commit, Sweep will create an pull request to fix the broken rule. 5 | rules: 6 | - "All docstrings and comments should be up to date." 7 | - "There should be no commented out code." 8 | - "Code should adhere to a consistent code style." 9 | - "There should be no debug log or print statements in production code." 10 | - "There should be no unused variables or functions." 11 | - "There should be no unnecessary imports." 12 | 13 | # This is the branch that Sweep will develop from and make pull requests to. Most people use 'main' or 'master' but some users also use 'dev' or 'staging'. 14 | branch: 'main' 15 | 16 | # By default Sweep will read the logs and outputs from your existing Github Actions. To disable this, set this to false. 17 | gha_enabled: True 18 | 19 | # This is the description of your project. It will be used by sweep when creating PRs. You can tell Sweep what's unique about your project, what frameworks you use, or anything else you want. 20 | # 21 | # Example: 22 | # 23 | # description: sweepai/sweep is a python project. The main api endpoints are in sweepai/api.py. Write code that adheres to PEP8. 24 | description: '' 25 | 26 | # This sets whether to create pull requests as drafts. If this is set to True, then all pull requests will be created as drafts and GitHub Actions will not be triggered. 27 | draft: False 28 | 29 | # This is a list of directories that Sweep will not be able to edit. 30 | blocked_dirs: [] 31 | 32 | # This is a list of documentation links that Sweep will use to help it understand your code. You can add links to documentation for any packages you use here. 33 | # 34 | # Example: 35 | # 36 | # docs: 37 | # - PyGitHub: ["https://pygithub.readthedocs.io/en/latest/", "We use pygithub to interact with the GitHub API"] 38 | docs: [] 39 | 40 | # Sandbox executes commands in a sandboxed environment to validate code changes after every edit to guarantee pristine code. For more details, see the [Sandbox](./sandbox) page. 41 | sandbox: 42 | install: 43 | - trunk init 44 | check: 45 | - trunk fmt {file_path} 46 | - trunk check --fix --print-failures {file_path} 47 | -------------------------------------------------------------------------------- /xmake.lua: -------------------------------------------------------------------------------- 1 | includes("VC-LTL5.lua") 2 | 3 | add_rules("mode.debug", "mode.release") 4 | 5 | set_warnings("more") 6 | 7 | add_defines("WIN32", "_WIN32") 8 | add_defines("UNICODE", "_UNICODE", "_CRT_SECURE_NO_WARNINGS", "_CRT_NONSTDC_NO_DEPRECATE") 9 | 10 | if is_mode("release") then 11 | add_defines("NDEBUG") 12 | add_cxflags("/O2", "/Os", "/Gy", "/MT", "/EHsc", "/fp:precise") 13 | add_ldflags("/DYNAMICBASE", "/LTCG") 14 | end 15 | 16 | add_cxflags("/utf-8") 17 | 18 | add_links("gdiplus", "kernel32", "user32", "gdi32", "winspool", "comdlg32") 19 | add_links("advapi32", "shell32", "ole32", "oleaut32", "uuid", "odbc32", "odbccp32") 20 | 21 | target("minhook") 22 | set_kind("static") 23 | add_files("minhook/src/**.c") 24 | add_includedirs("minhook/include", {public=true}) 25 | 26 | target("vivaldi_plus") 27 | set_kind("shared") 28 | set_targetdir("$(buildir)/release") 29 | set_basename("version") 30 | add_deps("minhook") 31 | add_files("src/*.cpp") 32 | add_files("src/*.rc") 33 | add_links("user32") 34 | after_build(function (target) 35 | os.rm("$(buildir)/release/version.exp") 36 | os.rm("$(buildir)/release/version.lib") 37 | end) 38 | --------------------------------------------------------------------------------