├── .github ├── FUNDING.yml └── workflows │ └── build.yml ├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENSE ├── README.md ├── build.ps1 ├── docs ├── logo │ ├── attribute.md │ ├── injection.png │ ├── proxinject.xcf │ └── window.png └── screenshot.png ├── resources ├── proxinject.ico ├── proxinject.png └── proxinject.rc ├── setup.nsi └── src ├── common ├── async_io.hpp ├── minhook.hpp ├── queue.hpp ├── schema.hpp ├── utils.hpp ├── version.hpp.in └── winraii.hpp ├── injectee ├── client.hpp ├── hook.hpp ├── injectee.cpp ├── services.inc ├── socks5.hpp └── winnet.hpp ├── injector ├── injector.hpp ├── injector_cli.cpp ├── injector_cli.hpp ├── injector_gui.cpp ├── injector_gui.hpp ├── server.hpp └── ui_elements │ ├── dynamic_list.cpp │ ├── dynamic_list.hpp │ ├── text_box.cpp │ ├── text_box.hpp │ ├── tooltip.cpp │ └── tooltip.hpp └── wow64 └── address_dumper.cpp /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: pragmatwice # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - v* 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | 13 | jobs: 14 | build: 15 | runs-on: windows-2022 16 | 17 | strategy: 18 | matrix: 19 | build_type: [ Debug, Release ] 20 | arch: [ Win32, x64 ] 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | with: 25 | fetch-depth: 0 26 | 27 | - name: Build 28 | run: .\build.ps1 -mode ${{ matrix.build_type }} -arch ${{ matrix.arch }} 29 | 30 | - name: Set variables 31 | run: | 32 | echo "PROXINJECT_VERSION=$(git describe --tags)" >> $Env:GITHUB_ENV 33 | echo "SHORT_SHA=$('${{ github.sha }}'.Substring(0,7))" >> $Env:GITHUB_ENV 34 | 35 | - name: Upload 36 | uses: actions/upload-artifact@v3.0.0 37 | with: 38 | name: proxinject-snapshot-${{ env.SHORT_SHA }}-${{ matrix.build_type }}-${{ matrix.arch }} 39 | path: | 40 | ${{github.workspace}}\release 41 | retention-days: 30 42 | 43 | - name: Pack 44 | run: | 45 | makensis /DVERSION=${{ env.PROXINJECT_VERSION }} .\setup.nsi 46 | if: ${{ matrix.build_type == 'Release' }} 47 | 48 | - name: Upload Package 49 | if: ${{ matrix.build_type == 'Release' }} 50 | uses: actions/upload-artifact@v3.0.0 51 | with: 52 | name: proxinject-snapshot-${{ env.SHORT_SHA }}-${{ matrix.arch }}-installer 53 | path: | 54 | ${{github.workspace}}\proxinjectSetup.exe 55 | retention-days: 30 56 | 57 | - name: Rename for Release 58 | if: ${{ startsWith(github.ref, 'refs/tags/v') && matrix.build_type == 'Release' }} 59 | run: | 60 | mv proxinjectSetup.exe proxinjectSetup-${{ env.PROXINJECT_VERSION }}-${{ matrix.arch }}.exe 61 | Compress-Archive release proxinject-${{ env.PROXINJECT_VERSION }}-${{ matrix.arch }}.zip 62 | 63 | - name: Release 64 | uses: softprops/action-gh-release@v1 65 | if: ${{ startsWith(github.ref, 'refs/tags/v') && matrix.build_type == 'Release' && matrix.arch == 'x64' }} 66 | with: 67 | prerelease: true 68 | files: | 69 | proxinjectSetup*.exe 70 | proxinject*.zip 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /out/ 2 | /.vs/ 3 | /.vscode/ 4 | /.idea/ 5 | /build*/ 6 | /release*/ 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2022 PragmaTwice 2 | # 3 | # Licensed under the Apache License, 4 | # Version 2.0(the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | cmake_minimum_required(VERSION 3.20) 17 | 18 | project(proxinject) 19 | 20 | option(PROXINJECTEE_ONLY "only build proxyinjectee" OFF) 21 | 22 | if(NOT WIN32) 23 | message(FATAL_ERROR "support Windows only") 24 | endif() 25 | 26 | set(CMAKE_CXX_STANDARD 20) 27 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 28 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 29 | 30 | include(FetchContent) 31 | 32 | FetchContent_Declare(minhook 33 | GIT_REPOSITORY https://github.com/TsudaKageyu/minhook 34 | GIT_TAG 49d03ad118cf7f6768c79a8f187e14b8f2a07f94 35 | ) 36 | 37 | set(BUILD_TESTS OFF CACHE BOOL "") 38 | FetchContent_Declare(protopuf 39 | GIT_REPOSITORY https://github.com/PragmaTwice/protopuf 40 | GIT_TAG v2.2.1 41 | ) 42 | 43 | FetchContent_Declare(argparse 44 | GIT_REPOSITORY https://github.com/p-ranav/argparse 45 | GIT_TAG v2.9 46 | ) 47 | 48 | FetchContent_Declare(spdlog 49 | GIT_REPOSITORY https://github.com/gabime/spdlog 50 | GIT_TAG v1.10.0 51 | ) 52 | 53 | FetchContent_MakeAvailable(minhook protopuf) 54 | if(NOT PROXINJECTEE_ONLY) 55 | FetchContent_MakeAvailable(argparse spdlog) 56 | endif() 57 | 58 | FetchContent_Declare(asio 59 | GIT_REPOSITORY https://github.com/chriskohlhoff/asio 60 | GIT_TAG asio-1-22-2 61 | ) 62 | 63 | FetchContent_GetProperties(asio) 64 | if(NOT asio_POPULATED) 65 | FetchContent_Populate(asio) 66 | endif() 67 | 68 | if(NOT PROXINJECTEE_ONLY) 69 | set(ELEMENTS_ASIO_INCLUDE_DIR ${asio_SOURCE_DIR}/asio/include CACHE STRING "") 70 | set(ELEMENTS_BUILD_EXAMPLES OFF CACHE BOOL "") 71 | FetchContent_Declare(elements 72 | GIT_REPOSITORY https://github.com/PragmaTwice/elements 73 | GIT_TAG 8e4dfff096892ba16e437baaa0cecec3044bc231 74 | GIT_SUBMODULES_RECURSE ON 75 | ) 76 | 77 | FetchContent_MakeAvailable(elements) 78 | endif() 79 | 80 | set(WIN32_VERSION 0x0A00) 81 | 82 | execute_process(COMMAND git describe --tags 83 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 84 | RESULT_VARIABLE GIT_DESCRIBE_EXITCODE 85 | OUTPUT_VARIABLE PROXINJECT_VERSION) 86 | 87 | if(NOT GIT_DESCRIBE_EXITCODE EQUAL 0) 88 | message(FATAL_ERROR "error occurred while dumping version via `git describe`") 89 | endif() 90 | 91 | string(STRIP ${PROXINJECT_VERSION} PROXINJECT_VERSION) 92 | configure_file(src/common/version.hpp.in src/common/version.hpp) 93 | 94 | add_library(proxinject_common INTERFACE) 95 | target_include_directories(proxinject_common INTERFACE src/common ${CMAKE_BINARY_DIR}/src/common) 96 | target_compile_definitions(proxinject_common INTERFACE -D_WIN32_WINNT=${WIN32_VERSION} -DUNICODE) 97 | 98 | file(GLOB INJECTEE_SRCS src/injectee/*.cpp) 99 | 100 | add_library(proxinjectee SHARED ${INJECTEE_SRCS}) 101 | target_compile_features(proxinjectee PUBLIC cxx_std_20) 102 | target_link_libraries(proxinjectee PUBLIC minhook ws2_32 protopuf proxinject_common) 103 | target_include_directories(proxinjectee PUBLIC ${asio_SOURCE_DIR}/asio/include) 104 | 105 | if(NOT PROXINJECTEE_ONLY) 106 | file(GLOB INJECTOR_GUI_SRCS src/injector/injector_gui.cpp src/injector/ui_elements/*.cpp) 107 | 108 | set(ELEMENTS_APP_PROJECT proxinjector) 109 | set(ELEMENTS_APP_SOURCES ${INJECTOR_GUI_SRCS}) 110 | 111 | set(ELEMENTS_APP_ICON "resources/proxinject.rc") 112 | list(APPEND ELEMENTS_APP_SOURCES ${ELEMENTS_APP_ICON}) 113 | 114 | set(ELEMENTS_ROOT ${elements_SOURCE_DIR}) 115 | set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${ELEMENTS_ROOT}/cmake") 116 | include(ElementsConfigApp) 117 | 118 | target_compile_features(proxinjector PUBLIC cxx_std_20) 119 | target_link_libraries(proxinjector PUBLIC protopuf proxinject_common) 120 | target_include_directories(proxinjector PUBLIC ${asio_SOURCE_DIR}/asio/include) 121 | 122 | file(GLOB INJECTOR_CLI_SRCS src/injector/injector_cli.cpp) 123 | 124 | add_executable(proxinjector-cli ${INJECTOR_CLI_SRCS}) 125 | target_compile_features(proxinjector-cli PUBLIC cxx_std_20) 126 | target_link_libraries(proxinjector-cli PUBLIC protopuf argparse spdlog proxinject_common) 127 | target_include_directories(proxinjector-cli PUBLIC ${asio_SOURCE_DIR}/asio/include) 128 | endif() 129 | 130 | if(PROXINJECTEE_ONLY) 131 | add_executable(wow64-address-dumper src/wow64/address_dumper.cpp) 132 | endif() 133 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ "msvc_x64_x64" ], 8 | "buildRoot": "${projectDir}\\out\\build\\${name}", 9 | "installRoot": "${projectDir}\\out\\install\\${name}", 10 | "cmakeCommandArgs": "", 11 | "buildCommandArgs": "", 12 | "ctestCommandArgs": "" 13 | }, 14 | { 15 | "name": "x86-Debug", 16 | "generator": "Ninja", 17 | "configurationType": "Debug", 18 | "buildRoot": "${projectDir}\\out\\build\\${name}", 19 | "installRoot": "${projectDir}\\out\\install\\${name}", 20 | "cmakeCommandArgs": "", 21 | "buildCommandArgs": "", 22 | "ctestCommandArgs": "", 23 | "inheritEnvironments": [ "msvc_x86" ] 24 | }, 25 | { 26 | "name": "x64-Release", 27 | "generator": "Ninja", 28 | "configurationType": "RelWithDebInfo", 29 | "buildRoot": "${projectDir}\\out\\build\\${name}", 30 | "installRoot": "${projectDir}\\out\\install\\${name}", 31 | "cmakeCommandArgs": "", 32 | "buildCommandArgs": "", 33 | "ctestCommandArgs": "", 34 | "inheritEnvironments": [ "msvc_x64_x64" ] 35 | }, 36 | { 37 | "name": "x86-Release", 38 | "generator": "Ninja", 39 | "configurationType": "RelWithDebInfo", 40 | "buildRoot": "${projectDir}\\out\\build\\${name}", 41 | "installRoot": "${projectDir}\\out\\install\\${name}", 42 | "cmakeCommandArgs": "", 43 | "buildCommandArgs": "", 44 | "ctestCommandArgs": "", 45 | "inheritEnvironments": [ "msvc_x86" ] 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | logo 2 | 3 | # proxinject 4 | [![Build](https://github.com/PragmaTwice/proxinject/actions/workflows/build.yml/badge.svg)](https://github.com/PragmaTwice/proxinject/actions/workflows/build.yml) 5 | [![Release](https://shields.io/github/v/release/PragmaTwice/proxinject?display_name=tag&include_prereleases)](https://github.com/PragmaTwice/proxinject/releases) 6 | [![WinGet](https://img.shields.io/badge/winget-proxinject-blue)](https://github.com/microsoft/winget-pkgs/tree/master/manifests/p/PragmaTwice/proxinject) 7 | 8 | *A socks5 proxy injection tool for **Windows**: just select some processes and make them proxy-able!* 9 | 10 | ## Preview 11 | 12 | ### proxinject GUI 13 | 14 | ![screenshot](./docs/screenshot.png) 15 | 16 | ### proxinject CLI 17 | ``` 18 | $ ./proxinjector-cli -h 19 | Usage: proxinjector-cli [options] 20 | 21 | A socks5 proxy injection tool for Windows: just select some processes and make them proxy-able! 22 | Please visit https://github.com/PragmaTwice/proxinject for more information. 23 | 24 | Optional arguments: 25 | -h --help shows help message and exits [default: false] 26 | -v --version prints version information and exits [default: false] 27 | -i --pid pid of a process to inject proxy (integer) [default: {}] 28 | -n --name short filename of a process with wildcard matching to inject proxy (string, without directory and file extension, e.g. `python`, `py*`, `py??on`) [default: {}] 29 | -P --path full filename of a process with wildcard matching to inject proxy (string, with directory and file extension, e.g. `C:/programs/python.exe`, `C:/programs/*.exe`) [default: {}] 30 | -r --name-regexp regular expression for short filename of a process to inject proxy (string, without directory and file extension, e.g. `python`, `py.*|exp.*`) [default: {}] 31 | -R --path-regexp regular expression for full filename of a process to inject proxy (string, with directory and file extension, e.g. `C:/programs/python.exe`, `C:/programs/(a|b).*\.exe`) [default: {}] 32 | -e --exec command line started with an executable to create a new process and inject proxy (string, e.g. `python` or `C:\Program Files\a.exe --some-option`) [default: {}] 33 | -l --enable-log enable logging for network connections [default: false] 34 | -p --set-proxy set a proxy address for network connections (string, e.g. `127.0.0.1:1080`) [default: ""] 35 | -w --new-console-window create a new console window while a new console process is executed in `-e` [default: false] 36 | -s --subprocess inject subprocesses created by these already injected processes [default: false] 37 | ``` 38 | 39 | ## How to Install 40 | 41 | Choose whichever method you like: 42 | 43 | - Download the latest portable archive (`.zip`) or installer (`.exe`) from the [Releases Page](https://github.com/PragmaTwice/proxinject/releases), OR 44 | - Type `winget install PragmaTwice.proxinject` in the terminal ([winget](https://github.com/microsoft/winget-cli) is required) 45 | 46 | Or build from source (not recommended for non-professionals): 47 | 48 | ```sh 49 | # make sure your develop environment is well configured in powershell 50 | git clone https://github.com/PragmaTwice/proxinject.git 51 | cd proxinject 52 | ./build.ps1 -mode Release -arch x64 # build the project via CMake and msbuild 53 | # your built binaries are now in the `./release` directory, enjoy it now! 54 | makensis /DVERSION=$(git describe --tags) setup.nsi # (optional) genrate an installer via NSIS 55 | ``` 56 | 57 | ## Development Dependencies 58 | 59 | ### environments: 60 | 61 | - C++ compiler (with C++20 support, currently MSVC) 62 | - Windows SDK (with winsock2 support) 63 | - CMake 3 64 | 65 | ### libraries: 66 | (you do not need to download/install them manually) 67 | 68 | #### proxinjectee 69 | - minhook 70 | - asio (standalone) 71 | - PragmaTwice/protopuf 72 | 73 | #### proxinjector GUI 74 | - asio (standalone) 75 | - PragmaTwice/protopuf 76 | - cycfi/elements 77 | 78 | #### proxinjector CLI 79 | - asio (standalone) 80 | - PragmaTwice/protopuf 81 | - p-ranav/argparse 82 | - gabime/spdlog 83 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$build_dir = "build", 3 | [string]$release_dir = "release", 4 | [string]$mode = "Release", 5 | [string]$arch = "x64", 6 | [switch]$skip_cmake = $false 7 | ) 8 | 9 | mkdir $build_dir/Win32 -ea 0 10 | mkdir $build_dir/x64 -ea 0 11 | mkdir $release_dir -ea 0 12 | 13 | if(($arch -ne "Win32") -and ($arch -ne "x64")) { 14 | echo "arch should be either Win32 or x64" 15 | exit 1 16 | } 17 | 18 | if(!($skip_cmake)) { 19 | if($arch -eq "x64") { 20 | $win32_injectee_only="-DPROXINJECTEE_ONLY=ON" 21 | } else { 22 | $win32_injectee_only="" 23 | } 24 | 25 | if($arch -eq "x64") { 26 | cmake -DCMAKE_BUILD_TYPE="$mode" -A x64 -S . -B $build_dir/x64 27 | } 28 | cmake -DCMAKE_BUILD_TYPE="$mode" $win32_injectee_only -A Win32 -S . -B $build_dir/Win32 29 | } 30 | 31 | if($arch -eq "x64") { 32 | cmake --build $build_dir/x64 --config $mode -j $env:NUMBER_OF_PROCESSORS 33 | } 34 | cmake --build $build_dir/Win32 --config $mode -j $env:NUMBER_OF_PROCESSORS 35 | 36 | cp $build_dir/$arch/$mode/* $release_dir -Force 37 | cp $build_dir/$arch/*.dll $release_dir -Force 38 | cp $build_dir/$arch/resources $release_dir -Recurse -Force 39 | 40 | if($arch -eq "x64") { 41 | cp $build_dir/Win32/$mode/proxinjectee.dll $release_dir/proxinjectee32.dll -Force 42 | cp $build_dir/Win32/$mode/wow64-address-dumper.exe $release_dir -Force 43 | } 44 | 45 | cp LICENSE $release_dir -Recurse -Force 46 | -------------------------------------------------------------------------------- /docs/logo/attribute.md: -------------------------------------------------------------------------------- 1 | Window icons created by HideMaru - Flaticon 2 | 3 | Injection icons created by Andrean Prabowo - Flaticon 4 | -------------------------------------------------------------------------------- /docs/logo/injection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PragmaTwice/proxinject/b4b29ee8b0d26efe234e141f42f2703ad376dda2/docs/logo/injection.png -------------------------------------------------------------------------------- /docs/logo/proxinject.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PragmaTwice/proxinject/b4b29ee8b0d26efe234e141f42f2703ad376dda2/docs/logo/proxinject.xcf -------------------------------------------------------------------------------- /docs/logo/window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PragmaTwice/proxinject/b4b29ee8b0d26efe234e141f42f2703ad376dda2/docs/logo/window.png -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PragmaTwice/proxinject/b4b29ee8b0d26efe234e141f42f2703ad376dda2/docs/screenshot.png -------------------------------------------------------------------------------- /resources/proxinject.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PragmaTwice/proxinject/b4b29ee8b0d26efe234e141f42f2703ad376dda2/resources/proxinject.ico -------------------------------------------------------------------------------- /resources/proxinject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PragmaTwice/proxinject/b4b29ee8b0d26efe234e141f42f2703ad376dda2/resources/proxinject.png -------------------------------------------------------------------------------- /resources/proxinject.rc: -------------------------------------------------------------------------------- 1 | IDI_ELEMENTS_APP_ICON ICON DISCARDABLE "proxinject.ico" 2 | -------------------------------------------------------------------------------- /setup.nsi: -------------------------------------------------------------------------------- 1 | ;-------------------------------- 2 | ; Includes 3 | 4 | !include "MUI2.nsh" 5 | !include "logiclib.nsh" 6 | 7 | ;-------------------------------- 8 | ; Custom defines 9 | 10 | !define NAME "proxinject" 11 | !define APPFILE "proxinjector.exe" 12 | !define SLUG "${NAME} ${VERSION}" 13 | 14 | ;-------------------------------- 15 | ; General 16 | 17 | Name "${NAME}" 18 | OutFile "${NAME}Setup.exe" 19 | InstallDir "$Appdata\${NAME}" 20 | InstallDirRegKey HKCU "Software\${NAME}" "" 21 | RequestExecutionLevel user 22 | 23 | ;-------------------------------- 24 | ; UI 25 | 26 | !define MUI_HEADERIMAGE 27 | !define MUI_FINISHPAGE_NOAUTOCLOSE 28 | !define MUI_UNFINISHPAGE_NOAUTOCLOSE 29 | !define MUI_ABORTWARNING 30 | !define MUI_WELCOMEPAGE_TITLE "${SLUG} Setup" 31 | 32 | ;-------------------------------- 33 | ; Pages 34 | 35 | ; Installer pages 36 | !insertmacro MUI_PAGE_WELCOME 37 | !insertmacro MUI_PAGE_LICENSE "LICENSE" 38 | !insertmacro MUI_PAGE_COMPONENTS 39 | !insertmacro MUI_PAGE_DIRECTORY 40 | !insertmacro MUI_PAGE_INSTFILES 41 | !insertmacro MUI_PAGE_FINISH 42 | 43 | ; Uninstaller pages 44 | !insertmacro MUI_UNPAGE_CONFIRM 45 | !insertmacro MUI_UNPAGE_INSTFILES 46 | 47 | ; Set UI language 48 | !insertmacro MUI_LANGUAGE "English" 49 | 50 | ;-------------------------------- 51 | ; Section - Install App 52 | 53 | Section "-hidden app" 54 | SectionIn RO 55 | SetOutPath "$INSTDIR" 56 | File /r "release\*.*" 57 | WriteRegStr HKCU "Software\${NAME}" "" $INSTDIR 58 | WriteUninstaller "$INSTDIR\uninstall.exe" 59 | SectionEnd 60 | 61 | ;-------------------------------- 62 | ; Section - Shortcut 63 | 64 | Section "Desktop Shortcut" DeskShort 65 | CreateShortCut "$DESKTOP\${NAME}.lnk" "$INSTDIR\${APPFILE}" 66 | SectionEnd 67 | 68 | Section "Start Menu Shortcut" StartShort 69 | CreateDirectory "$SMPROGRAMS\${NAME}" 70 | CreateShortCut "$SMPROGRAMS\${NAME}\${NAME}.lnk" "$INSTDIR\${APPFILE}" 71 | CreateShortCut "$SMPROGRAMS\${NAME}\Uninstall ${NAME}.lnk" "$INSTDIR\uninstall.exe" 72 | SectionEnd 73 | 74 | ;-------------------------------- 75 | ; Descriptions 76 | 77 | ;Language strings 78 | LangString DESC_DeskShort ${LANG_ENGLISH} "Create Shortcut on Dekstop." 79 | LangString DESC_StartShort ${LANG_ENGLISH} "Create Shortcut on Start Menu." 80 | 81 | ;Assign language strings to sections 82 | !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN 83 | !insertmacro MUI_DESCRIPTION_TEXT ${DeskShort} $(DESC_DeskShort) 84 | !insertmacro MUI_DESCRIPTION_TEXT ${StartShort} $(DESC_StartShort) 85 | !insertmacro MUI_FUNCTION_DESCRIPTION_END 86 | 87 | ;-------------------------------- 88 | ; Section - Uninstaller 89 | 90 | Section "Uninstall" 91 | Delete "$DESKTOP\${NAME}.lnk" 92 | RMDir /r "$SMPROGRAMS\${NAME}" 93 | Delete "$INSTDIR\uninstall.exe" 94 | RMDir /r "$INSTDIR" 95 | DeleteRegKey /ifempty HKCU "Software\${NAME}" 96 | SectionEnd 97 | -------------------------------------------------------------------------------- /src/common/async_io.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_COMMON_ASYNC_IO 17 | #define PROXINJECT_COMMON_ASYNC_IO 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | namespace ip = asio::ip; 24 | using tcp = asio::ip::tcp; 25 | 26 | template 27 | asio::awaitable async_read_message(Stream &s) { 28 | std::int32_t len = 0; 29 | co_await asio::async_read(s, asio::buffer(&len, sizeof(len)), 30 | asio::use_awaitable); 31 | 32 | std::vector buf(len); 33 | co_await asio::async_read(s, asio::buffer(buf, len), asio::use_awaitable); 34 | 35 | auto [msg, remains] = 36 | pp::message_coder::decode(std::span(buf.begin(), buf.end())); 37 | co_return msg; 38 | } 39 | 40 | template 41 | asio::awaitable async_write_message(Stream &s, const Message &msg) { 42 | std::int32_t len = pp::skipper>::encode_skip(msg); 43 | 44 | std::vector buf(len); 45 | pp::message_coder::encode(msg, std::span(buf.begin(), buf.end())); 46 | 47 | co_await asio::async_write(s, asio::buffer(&len, sizeof(len)), 48 | asio::use_awaitable); 49 | co_await asio::async_write(s, asio::buffer(buf, len), asio::use_awaitable); 50 | } 51 | 52 | inline const auto localhost = ip::address::from_string("127.0.0.1"); 53 | 54 | inline const auto auto_endpoint = tcp::endpoint(localhost, 0); 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/common/minhook.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_COMMON_MINHOOK 17 | #define PROXINJECT_COMMON_MINHOOK 18 | 19 | #include 20 | 21 | struct minhook { 22 | struct status { 23 | MH_STATUS v; 24 | 25 | status(MH_STATUS v) : v(v) {} 26 | 27 | operator MH_STATUS() const { return v; } 28 | bool ok() const { return v == MH_OK; } 29 | bool error() const { return v != MH_OK; } 30 | }; 31 | 32 | static status init() { return MH_Initialize(); } 33 | 34 | static status deinit() { return MH_Uninitialize(); } 35 | 36 | template static status enable(F *api) { 37 | return MH_EnableHook(reinterpret_cast(api)); 38 | } 39 | 40 | template static status disable(F *api) { 41 | return MH_DisableHook(reinterpret_cast(api)); 42 | } 43 | 44 | static constexpr inline void *all_hooks = MH_ALL_HOOKS; 45 | 46 | static status enable() { return enable(all_hooks); } 47 | 48 | static status disable() { return disable(all_hooks); } 49 | 50 | template 51 | static status create(F *target, F *detour, F *&original) { 52 | return MH_CreateHook(reinterpret_cast(target), 53 | reinterpret_cast(detour), 54 | reinterpret_cast(&original)); 55 | } 56 | 57 | template static status remove(F *target) { 58 | return MH_RemoveHook(reinterpret_cast(target)); 59 | } 60 | 61 | template struct api { 62 | using F = decltype(syscall); 63 | 64 | static inline F original = nullptr; 65 | 66 | static status create() { 67 | return minhook::create(syscall, derived::detour, original); 68 | } 69 | 70 | static status remove() { return minhook::remove(syscall); } 71 | }; 72 | }; 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /src/common/queue.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_COMMON_QUEUE 17 | #define PROXINJECT_COMMON_QUEUE 18 | 19 | #include 20 | #include 21 | 22 | template using channel = asio::experimental::channel; 23 | 24 | template class blocking_queue { 25 | std::mutex _sync; 26 | std::queue _qu; 27 | channel chan; 28 | 29 | public: 30 | blocking_queue(asio::io_context &ctx, size_t max) 31 | : chan(ctx.get_executor(), max) {} 32 | 33 | void push(const T &item) { 34 | std::unique_lock lock(_sync); 35 | _qu.push(item); 36 | asio::co_spawn(chan.get_executor(), 37 | chan.async_send(asio::error_code{}, asio::use_awaitable), 38 | asio::detached); 39 | } 40 | 41 | asio::awaitable pop() { 42 | co_await chan.async_receive(asio::use_awaitable); 43 | std::unique_lock lock(_sync); 44 | T item = std::move(_qu.front()); 45 | _qu.pop(); 46 | co_return item; 47 | } 48 | 49 | void cancel() { chan.cancel(); } 50 | 51 | ~blocking_queue() { chan.close(); } 52 | }; 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /src/common/schema.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_COMMON_SCHEMA 17 | #define PROXINJECT_COMMON_SCHEMA 18 | 19 | #include 20 | 21 | using pp::operator""_f; 22 | 23 | using IpAddr = 24 | pp::message, pp::bytes_field<"v6_addr", 2>, 25 | pp::string_field<"domain", 4>, pp::uint32_field<"port", 3>>; 26 | 27 | inline std::pair to_asio(const IpAddr &ip) { 28 | if (const auto &v = ip["v4_addr"_f]) { 29 | return {ip::address_v4(*v).to_string(), *ip["port"_f]}; 30 | } else if (const auto &v = ip["v6_addr"_f]) { 31 | ip::address_v6::bytes_type addr; 32 | std::copy(v->begin(), v->end(), addr.begin()); 33 | return {ip::address_v6(addr).to_string(), *ip["port"_f]}; 34 | } else { 35 | const auto &domain = ip["domain"_f]; 36 | return {*domain, *ip["port"_f]}; 37 | } 38 | } 39 | 40 | inline IpAddr from_asio(const ip::address &addr, std::uint16_t port) { 41 | if (addr.is_v4()) { 42 | return IpAddr{addr.to_v4().to_uint(), {}, {}, port}; 43 | } else { 44 | auto v = addr.to_v6().to_bytes(); 45 | return IpAddr{{}, std::vector{v.begin(), v.end()}, {}, port}; 46 | } 47 | } 48 | 49 | template auto &operator<<(OS &stream, const IpAddr &addr) { 50 | auto [address, port] = to_asio(addr); 51 | return stream << address << ":" << port; 52 | } 53 | 54 | using InjecteeConnect = pp::message< 55 | pp::uint32_field<"handle", 1>, pp::message_field<"addr", 2, IpAddr>, 56 | pp::message_field<"proxy", 3, IpAddr>, pp::string_field<"syscall", 4>>; 57 | 58 | using InjecteeMessage = 59 | pp::message, 60 | pp::message_field<"connect", 2, InjecteeConnect>, 61 | pp::uint32_field<"pid", 3>, pp::uint32_field<"subpid", 4>>; 62 | 63 | using InjectorConfig = 64 | pp::message, pp::bool_field<"log", 2>, 65 | pp::bool_field<"subprocess", 3>>; 66 | 67 | using InjectorMessage = 68 | pp::message, 69 | pp::message_field<"config", 2, InjectorConfig>>; 70 | 71 | template 72 | M create_message(T &&v) { 73 | M msg; 74 | 75 | msg["opcode"_f] = S.data; 76 | msg.get() = std::forward(v); 77 | 78 | return msg; 79 | } 80 | 81 | template 82 | M::template get_type_by_name::base_type compare_message(const M &msg) { 83 | if (msg["opcode"_f] == S.data) { 84 | return msg.get_base(); 85 | } 86 | 87 | return std::nullopt; 88 | } 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /src/common/utils.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_COMMON_UTILS 17 | #define PROXINJECT_COMMON_UTILS 18 | 19 | #include 20 | #include 21 | 22 | // trim from start (in place) 23 | inline void ltrim(std::string &s) { 24 | s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { 25 | return !std::isspace(ch); 26 | })); 27 | } 28 | 29 | // trim from end (in place) 30 | inline void rtrim(std::string &s) { 31 | s.erase(std::find_if(s.rbegin(), s.rend(), 32 | [](unsigned char ch) { return !std::isspace(ch); }) 33 | .base(), 34 | s.end()); 35 | } 36 | 37 | // trim from both ends (in place) 38 | inline void trim(std::string &s) { 39 | ltrim(s); 40 | rtrim(s); 41 | } 42 | 43 | // trim from start (copying) 44 | inline std::string ltrim_copy(std::string s) { 45 | ltrim(s); 46 | return s; 47 | } 48 | 49 | // trim from end (copying) 50 | inline std::string rtrim_copy(std::string s) { 51 | rtrim(s); 52 | return s; 53 | } 54 | 55 | // trim from both ends (copying) 56 | inline std::string trim_copy(std::string s) { 57 | trim(s); 58 | return s; 59 | } 60 | 61 | inline bool all_of_digit(const auto &v) { 62 | return std::all_of(v.begin(), v.end(), 63 | [](auto c) { return std::isdigit(c); }); 64 | } 65 | 66 | // utf8_encode/decode from cycfi::elements 67 | 68 | // Convert a wide Unicode string to an UTF8 string 69 | inline std::string utf8_encode(std::wstring const &wstr) { 70 | if (wstr.empty()) 71 | return {}; 72 | int size = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), 73 | nullptr, 0, nullptr, nullptr); 74 | std::string result(size, 0); 75 | WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &result[0], size, 76 | nullptr, nullptr); 77 | return result; 78 | } 79 | 80 | // Convert an UTF8 string to a wide Unicode String 81 | inline std::wstring utf8_decode(std::string const &str) { 82 | if (str.empty()) 83 | return {}; 84 | int size = 85 | MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), nullptr, 0); 86 | std::wstring result(size, 0); 87 | MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &result[0], size); 88 | return result; 89 | } 90 | 91 | inline bool filename_wildcard_match(const char *pattern, const char *str) { 92 | for (; *pattern; ++pattern) { 93 | switch (*pattern) { 94 | case '?': 95 | if (*str == 0) 96 | return false; 97 | ++str; 98 | break; 99 | case '*': { 100 | if (pattern[1] == 0) 101 | return true; 102 | for (const char *ptr = str; *ptr; ++ptr) 103 | if (filename_wildcard_match(pattern + 1, ptr)) 104 | return true; 105 | return false; 106 | } 107 | case '/': 108 | case '\\': 109 | if (*str != '/' && *str != '\\') 110 | return false; 111 | ++str; 112 | break; 113 | default: 114 | if (std::tolower(*str) != std::tolower(*pattern)) 115 | return false; 116 | ++str; 117 | } 118 | } 119 | return *str == 0; 120 | } 121 | 122 | inline std::size_t replace_all_inplace(std::string &inout, 123 | std::string_view what, 124 | std::string_view with) { 125 | std::size_t count{}; 126 | for (std::string::size_type pos{}; 127 | inout.npos != (pos = inout.find(what.data(), pos, what.length())); 128 | pos += with.length(), ++count) { 129 | inout.replace(pos, what.length(), with.data(), with.length()); 130 | } 131 | return count; 132 | } 133 | 134 | inline std::string replace_all(const std::string &input, std::string_view what, 135 | std::string_view with) { 136 | std::string result = input; 137 | replace_all_inplace(result, what, with); 138 | return result; 139 | } 140 | 141 | inline bool regex_match_filename(const std::string &pattern, 142 | const std::string &input) { 143 | bool matched = false; 144 | 145 | try { 146 | std::regex re(pattern, std::regex_constants::icase | 147 | std::regex_constants::ECMAScript); 148 | matched = std::regex_match(replace_all(input, "/", "\\"), re) || 149 | std::regex_match(replace_all(input, "\\", "/"), re); 150 | } catch (const std::regex_error &) { 151 | } 152 | 153 | return matched; 154 | } 155 | 156 | inline const std::wstring port_mapping_name = L"PROXINJECT_PORT_IPC_"; 157 | 158 | inline std::wstring get_port_mapping_name(DWORD pid) { 159 | return port_mapping_name + std::to_wstring(pid); 160 | } 161 | 162 | inline std::string proxinject_copyright(const std::string &version) { 163 | return "proxinject " + version + "\n\n" + "Copyright (c) PragmaTwice\n" + 164 | "Licensed under the Apache License, Version 2.0"; 165 | } 166 | 167 | inline std::string proxinject_description = 168 | "A socks5 proxy injection tool for Windows: just select some processes " 169 | "and make them proxy-able!\nPlease visit " 170 | "https://github.com/PragmaTwice/proxinject for more information."; 171 | 172 | #endif 173 | -------------------------------------------------------------------------------- /src/common/version.hpp.in: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_COMMON_VERSION 17 | #define PROXINJECT_COMMON_VERSION 18 | 19 | #include 20 | 21 | inline const std::string proxinject_version = "@PROXINJECT_VERSION@"; 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/common/winraii.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_COMMON_WINRAII 17 | #define PROXINJECT_COMMON_WINRAII 18 | 19 | #include "tlhelp32.h" 20 | #include "utils.hpp" 21 | #include 22 | #include 23 | #include 24 | 25 | template struct static_function { 26 | template decltype(auto) operator()(T &&x) const { 27 | return f(std::forward(x)); 28 | } 29 | }; 30 | 31 | struct handle : std::unique_ptr> { 32 | using base_type = std::unique_ptr>; 33 | 34 | using base_type::base_type; 35 | 36 | handle(HANDLE hd) : base_type(hd) {} 37 | }; 38 | 39 | struct virtual_memory { 40 | void *const proc_handle; 41 | void *const mem_addr; 42 | const SIZE_T size_; 43 | 44 | virtual_memory(void *proc_handle, SIZE_T size) 45 | : proc_handle(proc_handle), 46 | mem_addr(VirtualAllocEx(proc_handle, nullptr, size, 47 | MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)), 48 | size_(size) {} 49 | 50 | virtual_memory(const virtual_memory &) = delete; 51 | virtual_memory(virtual_memory &&) = default; 52 | 53 | ~virtual_memory() { 54 | if (mem_addr) { 55 | VirtualFreeEx(proc_handle, mem_addr, 0, MEM_RELEASE); 56 | } 57 | } 58 | 59 | operator bool() const { return mem_addr != nullptr; } 60 | 61 | void *get() const { return mem_addr; } 62 | 63 | void *process_handle() const { return proc_handle; } 64 | 65 | SIZE_T size() const { return size_; } 66 | 67 | std::optional write(const void *buf, SIZE_T n) const { 68 | SIZE_T written_size; 69 | 70 | if (WriteProcessMemory(proc_handle, mem_addr, buf, n, &written_size)) { 71 | return written_size; 72 | } 73 | 74 | return std::nullopt; 75 | } 76 | 77 | auto write(const void *buf) { return write(buf, size_); } 78 | 79 | std::optional read(void *buf, SIZE_T n) const { 80 | SIZE_T read_size; 81 | 82 | if (ReadProcessMemory(proc_handle, mem_addr, buf, n, &read_size)) { 83 | return read_size; 84 | } 85 | 86 | return std::nullopt; 87 | } 88 | 89 | auto read(void *buf) { return read(buf, size_); } 90 | }; 91 | 92 | HMODULE get_current_module() { 93 | HMODULE mod = nullptr; 94 | 95 | GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, 96 | (LPCTSTR)get_current_module, &mod); 97 | 98 | return mod; 99 | } 100 | 101 | std::wstring get_current_filename() { 102 | wchar_t path[1024 + 1] = {}; 103 | 104 | GetModuleFileNameW(get_current_module(), path, 1024); 105 | 106 | return path; 107 | } 108 | 109 | template struct scope_ptr_bind { 110 | T *&ptr; 111 | 112 | scope_ptr_bind(T *&ptr, T *bind) : ptr(ptr) { ptr = bind; } 113 | ~scope_ptr_bind() { ptr = nullptr; } 114 | }; 115 | 116 | template void match_process(F &&f) { 117 | if (handle snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL)) { 118 | PROCESSENTRY32W entry = {sizeof(PROCESSENTRY32W)}; 119 | if (Process32FirstW(snapshot.get(), &entry)) { 120 | do { 121 | std::forward(f)(entry); 122 | } while (Process32NextW(snapshot.get(), &entry)); 123 | } 124 | } 125 | } 126 | 127 | inline handle create_mapping(const std::wstring &name, DWORD buf_size) { 128 | return CreateFileMappingW(INVALID_HANDLE_VALUE, // use paging file 129 | NULL, // default security 130 | PAGE_READWRITE, // read/write access 131 | 0, // maximum object size (high-order DWORD) 132 | buf_size, // maximum object size (low-order DWORD) 133 | name.c_str()); // name of mapping object 134 | } 135 | 136 | inline handle open_mapping(const std::wstring &name) { 137 | return OpenFileMappingW(FILE_MAP_ALL_ACCESS, // read/write access 138 | FALSE, // do not inherit the name 139 | name.c_str()); // name of mapping object 140 | } 141 | 142 | struct mapped_buffer : std::unique_ptr> { 143 | using base_type = std::unique_ptr>; 144 | 145 | mapped_buffer(HANDLE mapping, DWORD offset = 0, SIZE_T size = 0) 146 | : base_type(MapViewOfFile(mapping, // handle to map object 147 | FILE_MAP_ALL_ACCESS, // read/write permission 148 | 0, offset, size)) {} 149 | }; 150 | 151 | inline std::optional get_process_filepath(DWORD pid) { 152 | handle process = 153 | OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); 154 | 155 | DWORD size = MAX_PATH; 156 | auto filename = std::make_unique(size); 157 | if (!QueryFullProcessImageNameW(process.get(), 0, filename.get(), &size)) { 158 | return std::nullopt; 159 | } 160 | 161 | return std::wstring(filename.get(), size); 162 | } 163 | 164 | inline auto get_process_name(DWORD pid) { 165 | std::wstring result; 166 | match_process([pid, &result](const PROCESSENTRY32W &entry) { 167 | if (pid == entry.th32ProcessID) { 168 | result = entry.szExeFile; 169 | } 170 | }); 171 | 172 | std::string res_u8 = utf8_encode(result); 173 | if (res_u8.ends_with(".exe")) { 174 | return res_u8.substr(0, res_u8.size() - 4); 175 | } 176 | return res_u8; 177 | } 178 | 179 | template void match_process_by_name(F &&f) { 180 | match_process([&f](const PROCESSENTRY32W &entry) { 181 | std::string name_u8 = utf8_encode(entry.szExeFile); 182 | if (name_u8.ends_with(".exe")) { 183 | auto name = name_u8.substr(0, name_u8.size() - 4); 184 | std::forward(f)(name, entry.th32ProcessID); 185 | } 186 | }); 187 | } 188 | 189 | template void match_process_by_path(F &&f) { 190 | match_process([&f](const PROCESSENTRY32W &entry) { 191 | if (auto wpath = get_process_filepath(entry.th32ProcessID)) { 192 | auto path = utf8_encode(*wpath); 193 | std::forward(f)(path, entry.th32ProcessID); 194 | } 195 | }); 196 | } 197 | 198 | template void enumerate_child_pids(DWORD pid, F &&f) { 199 | match_process([pid, &f](const PROCESSENTRY32W &entry) { 200 | if (pid == entry.th32ParentProcessID) { 201 | std::forward(f)(entry.th32ProcessID); 202 | } 203 | }); 204 | } 205 | 206 | inline std::optional 207 | create_process(const std::wstring &command, DWORD creation_flags = 0) { 208 | STARTUPINFO startup_info{}; 209 | PROCESS_INFORMATION process_info{}; 210 | if (CreateProcessW(nullptr, std::wstring{command}.data(), nullptr, nullptr, 211 | false, creation_flags, nullptr, nullptr, &startup_info, 212 | &process_info) == 0) { 213 | return std::nullopt; 214 | } 215 | 216 | return process_info; 217 | } 218 | 219 | inline std::optional 220 | create_process(const std::string &path, DWORD creation_flags = 0) { 221 | auto wpath = utf8_decode(path); 222 | return create_process(wpath, creation_flags); 223 | } 224 | 225 | #endif 226 | -------------------------------------------------------------------------------- /src/injectee/client.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_INJECTEE_CLIENT 17 | #define PROXINJECT_INJECTEE_CLIENT 18 | 19 | #include "async_io.hpp" 20 | #include "queue.hpp" 21 | #include "schema.hpp" 22 | #include "winnet.hpp" 23 | 24 | struct injectee_config { 25 | InjectorConfig cfg; 26 | std::mutex mtx; 27 | 28 | void set(const InjectorConfig &config) { 29 | std::lock_guard guard(mtx); 30 | cfg = config; 31 | } 32 | 33 | InjectorConfig get() { 34 | std::lock_guard guard(mtx); 35 | return cfg; 36 | } 37 | 38 | void clear() { 39 | std::lock_guard guard(mtx); 40 | cfg = InjectorConfig{}; 41 | } 42 | }; 43 | 44 | struct injectee_client : std::enable_shared_from_this { 45 | tcp::socket socket_; 46 | tcp::endpoint endpoint_; 47 | asio::steady_timer timer_; 48 | blocking_queue &queue_; 49 | injectee_config &config_; 50 | 51 | injectee_client(asio::io_context &io_context, const tcp::endpoint &endpoint, 52 | blocking_queue &queue, 53 | injectee_config &config) 54 | : socket_(io_context), endpoint_(endpoint), timer_(io_context), 55 | queue_(queue), config_(config) { 56 | timer_.expires_at(std::chrono::steady_clock::time_point::max()); 57 | } 58 | 59 | asio::awaitable start() { 60 | co_await socket_.async_connect(endpoint_, asio::use_awaitable); 61 | 62 | co_await async_write_message( 63 | socket_, create_message(GetCurrentProcessId())); 64 | 65 | asio::co_spawn(socket_.get_executor(), reader(), asio::detached); 66 | asio::co_spawn(socket_.get_executor(), writer(), asio::detached); 67 | } 68 | 69 | asio::awaitable reader() { 70 | try { 71 | while (true) { 72 | auto msg = co_await async_read_message(socket_); 73 | asio::co_spawn( 74 | socket_.get_executor(), 75 | [this, msg = std::move(msg)] { return process(msg); }, 76 | asio::detached); 77 | } 78 | } catch (std::exception &) { 79 | stop(); 80 | } 81 | } 82 | 83 | asio::awaitable writer() { 84 | try { 85 | while (true) { 86 | InjecteeMessage msg = co_await queue_.pop(); 87 | co_await async_write_message(socket_, msg); 88 | } 89 | } catch (std::exception &) { 90 | stop(); 91 | } 92 | } 93 | 94 | asio::awaitable process(const InjectorMessage &msg) { 95 | if (auto v = compare_message<"config">(msg)) { 96 | config_.set(*v); 97 | } 98 | 99 | co_return; 100 | } 101 | 102 | void stop() { 103 | queue_.cancel(); 104 | config_.clear(); 105 | socket_.close(); 106 | timer_.cancel(); 107 | } 108 | }; 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /src/injectee/hook.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_INJECTEE_HOOK 17 | #define PROXINJECT_INJECTEE_HOOK 18 | 19 | #include "client.hpp" 20 | #include "minhook.hpp" 21 | #include "socks5.hpp" 22 | #include "utils.hpp" 23 | #include "winnet.hpp" 24 | #include 25 | #include 26 | #include 27 | 28 | inline blocking_queue *queue = nullptr; 29 | inline injectee_config *config = nullptr; 30 | inline std::map *nbio_map = nullptr; 31 | 32 | struct hook_ioctlsocket : minhook::api { 33 | static int WSAAPI detour(SOCKET s, long cmd, u_long FAR *argp) { 34 | if (nbio_map && cmd == FIONBIO) { 35 | (*nbio_map)[s] = *argp; 36 | } 37 | 38 | return original(s, cmd, argp); 39 | } 40 | }; 41 | struct hook_WSAAsyncSelect : minhook::api { 42 | static int WSAAPI detour(SOCKET s, HWND hWnd, u_int wMsg, long lEvent) { 43 | if (nbio_map) { 44 | (*nbio_map)[s] = true; 45 | } 46 | 47 | return original(s, hWnd, wMsg, lEvent); 48 | } 49 | }; 50 | struct hook_WSAEventSelect : minhook::api { 51 | static int WSAAPI detour(SOCKET s, WSAEVENT hEventObject, 52 | long lNetworkEvents) { 53 | if (nbio_map) { 54 | (*nbio_map)[s] = true; 55 | } 56 | 57 | return original(s, hEventObject, lNetworkEvents); 58 | } 59 | }; 60 | 61 | struct blocking_scope { 62 | SOCKET sock; 63 | 64 | blocking_scope(SOCKET s) : sock(s) { 65 | u_long nb = FALSE; 66 | hook_ioctlsocket::original(sock, FIONBIO, &nb); 67 | } 68 | ~blocking_scope() { 69 | if (nbio_map) { 70 | u_long nb = FALSE; 71 | if (auto iter = nbio_map->find(sock); iter != nbio_map->end()) { 72 | nb = (u_long)iter->second; 73 | } 74 | hook_ioctlsocket::original(sock, FIONBIO, &nb); 75 | } 76 | } 77 | 78 | blocking_scope(const blocking_scope &) = delete; 79 | blocking_scope(blocking_scope &&) = delete; 80 | }; 81 | 82 | template 83 | struct hook_connect_fn : minhook::api> { 84 | using base = minhook::api>; 85 | 86 | template 87 | static int WSAAPI detour(SOCKET s, const sockaddr *name, int namelen, 88 | T... args) { 89 | if (is_inet(name) && config && !is_localhost(name)) { 90 | auto cfg = config->get(); 91 | if (auto v = to_ip_addr(name)) { 92 | 93 | auto proxy = cfg["addr"_f]; 94 | auto log = cfg["log"_f]; 95 | if (queue && log && log.value()) { 96 | queue->push(create_message( 97 | InjecteeConnect{(std::uint32_t)s, *v, proxy, N})); 98 | } 99 | 100 | if (proxy) { 101 | if (auto [addr, addr_size] = to_sockaddr(*proxy); 102 | addr && !sockequal(addr.get(), name)) { 103 | blocking_scope scope(s); 104 | 105 | auto ret = base::original(s, addr.get(), addr_size, args...); 106 | if (ret) 107 | return ret; 108 | 109 | if (!socks5_handshake(s)) { 110 | shutdown(s, SD_BOTH); 111 | return SOCKET_ERROR; 112 | } 113 | if (socks5_request(s, name) != SOCKS_SUCCESS) { 114 | shutdown(s, SD_BOTH); 115 | return SOCKET_ERROR; 116 | } 117 | 118 | return 0; 119 | } 120 | } 121 | } 122 | } 123 | return base::original(s, name, namelen, args...); 124 | } 125 | }; 126 | 127 | struct hook_connect : hook_connect_fn {}; 128 | struct hook_WSAConnect : hook_connect_fn {}; 129 | 130 | struct hook_WSAConnectByList 131 | : minhook::api { 132 | static BOOL PASCAL detour(SOCKET s, PSOCKET_ADDRESS_LIST SocketAddress, 133 | LPDWORD LocalAddressLength, LPSOCKADDR LocalAddress, 134 | LPDWORD RemoteAddressLength, 135 | LPSOCKADDR RemoteAddress, const timeval *timeout, 136 | LPWSAOVERLAPPED Reserved) { 137 | if (config) { 138 | auto cfg = config->get(); 139 | auto proxy = cfg["addr"_f]; 140 | auto log = cfg["log"_f]; 141 | 142 | for (size_t i = 0; i < SocketAddress->iAddressCount; ++i) { 143 | LPSOCKADDR name = SocketAddress->Address[i].lpSockaddr; 144 | 145 | if (is_inet(name) && !is_localhost(name)) { 146 | if (auto v = to_ip_addr(name)) { 147 | 148 | if (queue && log && log.value()) { 149 | queue->push( 150 | create_message(InjecteeConnect{ 151 | (std::uint32_t)s, *v, proxy, "WSAConnectByList"})); 152 | } 153 | 154 | if (proxy) { 155 | if (auto [addr, addr_size] = to_sockaddr(*proxy); 156 | addr && !sockequal(addr.get(), name)) { 157 | blocking_scope scope(s); 158 | 159 | auto ret = hook_connect::original(s, addr.get(), addr_size); 160 | if (ret) 161 | return ret; 162 | 163 | if (!socks5_handshake(s)) { 164 | shutdown(s, SD_BOTH); 165 | continue; 166 | } 167 | if (socks5_request(s, name) != SOCKS_SUCCESS) { 168 | shutdown(s, SD_BOTH); 169 | continue; 170 | } 171 | 172 | *RemoteAddressLength = 173 | std::min(*RemoteAddressLength, (DWORD)addr_size); 174 | memcpy(RemoteAddress, addr.get(), *RemoteAddressLength); 175 | 176 | sockaddr local; 177 | int local_size = sizeof(local); 178 | getsockname(s, &local, &local_size); 179 | 180 | *LocalAddressLength = 181 | std::min(*LocalAddressLength, (DWORD)local_size); 182 | memcpy(LocalAddress, &local, *LocalAddressLength); 183 | 184 | return TRUE; 185 | } 186 | } 187 | } 188 | } 189 | } 190 | 191 | if (proxy) 192 | return FALSE; 193 | } 194 | 195 | return original(s, SocketAddress, LocalAddressLength, LocalAddress, 196 | RemoteAddressLength, RemoteAddress, timeout, Reserved); 197 | } 198 | }; 199 | 200 | inline const std::map service_map = { 201 | #define X(name, port, _) {name, port}, 202 | #include "services.inc" 203 | #undef X 204 | }; 205 | 206 | inline std::optional ipaddr_from_name(const std::string &nodename, 207 | const std::string &servicename) { 208 | std::uint16_t port; 209 | if (std::all_of(servicename.begin(), servicename.end(), 210 | [](char c) { return std::isdigit(c); })) { 211 | port = std::stoi(servicename); 212 | } else if (auto iter = service_map.find(servicename); 213 | iter != service_map.end()) { 214 | port = iter->second; 215 | } else { 216 | return std::nullopt; 217 | } 218 | 219 | asio::error_code ec; 220 | if (auto addr = ip::make_address(nodename, ec); !ec) { 221 | return from_asio(addr, port); 222 | } else { 223 | return IpAddr({}, {}, nodename, port); 224 | } 225 | } 226 | 227 | inline std::optional ipaddr_from_name(const std::wstring &nodename, 228 | const std::wstring &servicename) { 229 | 230 | return ipaddr_from_name(utf8_encode(nodename), utf8_encode(servicename)); 231 | } 232 | 233 | template 234 | struct hook_WSAConnectByName : minhook::api> { 235 | using base = minhook::api>; 236 | 237 | template 238 | static BOOL PASCAL detour(SOCKET s, Char *nodename, Char *servicename, 239 | LPDWORD LocalAddressLength, LPSOCKADDR LocalAddress, 240 | LPDWORD RemoteAddressLength, 241 | LPSOCKADDR RemoteAddress, 242 | const struct timeval *timeout, 243 | LPWSAOVERLAPPED Reserved) { 244 | if (config) { 245 | auto cfg = config->get(); 246 | auto proxy = cfg["addr"_f]; 247 | auto log = cfg["log"_f]; 248 | 249 | if (auto addr = ipaddr_from_name(nodename, servicename)) { 250 | if (queue && log && log.value()) { 251 | queue->push(create_message( 252 | InjecteeConnect{(std::uint32_t)s, addr, proxy, N})); 253 | } 254 | 255 | if (proxy) { 256 | if (auto [proxysa, addr_size] = to_sockaddr(*proxy); proxysa) { 257 | blocking_scope scope(s); 258 | 259 | auto ret = hook_connect::original(s, proxysa.get(), addr_size); 260 | if (ret) 261 | return ret; 262 | 263 | if (!socks5_handshake(s)) { 264 | shutdown(s, SD_BOTH); 265 | return FALSE; 266 | } 267 | if (socks5_request(s, *addr) != SOCKS_SUCCESS) { 268 | shutdown(s, SD_BOTH); 269 | return FALSE; 270 | } 271 | 272 | sockaddr peer; 273 | int peer_size = sizeof(peer); 274 | getsockname(s, &peer, &peer_size); 275 | 276 | *RemoteAddressLength = 277 | std::min(*RemoteAddressLength, (DWORD)peer_size); 278 | memcpy(RemoteAddress, &peer, *RemoteAddressLength); 279 | 280 | sockaddr local; 281 | int local_size = sizeof(local); 282 | getsockname(s, &local, &local_size); 283 | 284 | *LocalAddressLength = 285 | std::min(*LocalAddressLength, (DWORD)local_size); 286 | memcpy(LocalAddress, &local, *LocalAddressLength); 287 | 288 | return TRUE; 289 | } 290 | } 291 | } 292 | } 293 | 294 | return base::original(s, nodename, servicename, LocalAddressLength, 295 | LocalAddress, RemoteAddressLength, RemoteAddress, 296 | timeout, Reserved); 297 | } 298 | }; 299 | 300 | struct hook_WSAConnectByNameA 301 | : hook_WSAConnectByName {}; 302 | struct hook_WSAConnectByNameW 303 | : hook_WSAConnectByName {}; 304 | 305 | template struct startup_info_ptr_impl; 306 | 307 | template <> 308 | struct startup_info_ptr_impl : std::type_identity {}; 309 | 310 | template <> 311 | struct startup_info_ptr_impl : std::type_identity {}; 312 | 313 | template 314 | using startup_info_ptr = typename startup_info_ptr_impl::type; 315 | 316 | template 317 | struct hook_CreateProcess : minhook::api> { 318 | using base = minhook::api>; 319 | 320 | template 321 | static BOOL WINAPI detour(const Char *lpApplicationName, Char *lpCommandLine, 322 | LPSECURITY_ATTRIBUTES lpProcessAttributes, 323 | LPSECURITY_ATTRIBUTES lpThreadAttributes, 324 | BOOL bInheritHandles, DWORD dwCreationFlags, 325 | LPVOID lpEnvironment, 326 | const Char *lpCurrentDirectory, 327 | startup_info_ptr lpStartupInfo, 328 | LPPROCESS_INFORMATION lpProcessInformation) { 329 | BOOL res = base::original( 330 | lpApplicationName, lpCommandLine, lpProcessAttributes, 331 | lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, 332 | lpCurrentDirectory, lpStartupInfo, lpProcessInformation); 333 | 334 | if (res && config) { 335 | auto cfg = config->get(); 336 | auto subprocess = cfg["subprocess"_f]; 337 | 338 | if (queue && subprocess && subprocess.value()) { 339 | queue->push(create_message( 340 | lpProcessInformation->dwProcessId)); 341 | } 342 | } 343 | 344 | return res; 345 | } 346 | }; 347 | 348 | struct hook_CreateProcessA : hook_CreateProcess {}; 349 | struct hook_CreateProcessW : hook_CreateProcess {}; 350 | 351 | struct hook_ConnectEx { 352 | 353 | static inline LPFN_CONNECTEX ConnectEx = nullptr; 354 | 355 | static LPFN_CONNECTEX GetConnectEx() { 356 | WSADATA wsaData; 357 | WSAStartup(MAKEWORD(2, 2), &wsaData); 358 | 359 | SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 360 | DWORD numBytes = 0; 361 | GUID guid = WSAID_CONNECTEX; 362 | LPFN_CONNECTEX ConnectExPtr = nullptr; 363 | 364 | WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, (void *)&guid, sizeof(guid), 365 | (void *)&ConnectExPtr, sizeof(ConnectExPtr), &numBytes, nullptr, 366 | nullptr); 367 | 368 | closesocket(s); 369 | return ConnectExPtr; 370 | } 371 | 372 | static inline decltype(ConnectEx) original = nullptr; 373 | 374 | static BOOL PASCAL detour(SOCKET s, const struct sockaddr *name, int namelen, 375 | PVOID lpSendBuffer, DWORD dwSendDataLength, 376 | LPDWORD lpdwBytesSent, LPOVERLAPPED lpOverlapped) { 377 | if (is_inet(name) && config && !is_localhost(name)) { 378 | auto cfg = config->get(); 379 | if (auto v = to_ip_addr(name)) { 380 | 381 | auto proxy = cfg["addr"_f]; 382 | auto log = cfg["log"_f]; 383 | if (queue && log && log.value()) { 384 | queue->push(create_message( 385 | InjecteeConnect{(std::uint32_t)s, *v, proxy, "ConnectEx"})); 386 | } 387 | 388 | if (proxy) { 389 | if (auto [addr, addr_size] = to_sockaddr(*proxy); 390 | addr && !sockequal(addr.get(), name)) { 391 | blocking_scope scope(s); 392 | 393 | auto ret = hook_connect::original(s, addr.get(), addr_size); 394 | if (ret) 395 | return ret; 396 | 397 | if (!socks5_handshake(s)) { 398 | shutdown(s, SD_BOTH); 399 | return FALSE; 400 | } 401 | if (socks5_request(s, name) != SOCKS_SUCCESS) { 402 | shutdown(s, SD_BOTH); 403 | return FALSE; 404 | } 405 | 406 | if (lpSendBuffer) { 407 | int len = 408 | send(s, (const char *)lpSendBuffer, dwSendDataLength, 0); 409 | if (len == SOCKET_ERROR) { 410 | shutdown(s, SD_BOTH); 411 | return FALSE; 412 | } 413 | 414 | *lpdwBytesSent = len; 415 | } 416 | 417 | return TRUE; 418 | } 419 | } 420 | } 421 | } 422 | return original(s, name, namelen, lpSendBuffer, dwSendDataLength, 423 | lpdwBytesSent, lpOverlapped); 424 | } 425 | 426 | static minhook::status create() { 427 | ConnectEx = GetConnectEx(); 428 | return minhook::create(ConnectEx, detour, original); 429 | } 430 | 431 | static minhook::status remove() { return minhook::remove(ConnectEx); } 432 | }; 433 | 434 | template minhook::status create_hooks() { 435 | if (auto status = T::create(); status.error()) { 436 | return status; 437 | } 438 | 439 | if constexpr (sizeof...(Ts) > 0) { 440 | return create_hooks(); 441 | } 442 | 443 | return MH_OK; 444 | } 445 | 446 | inline minhook::status hook_create_all() { 447 | return create_hooks(); 452 | } 453 | 454 | #endif 455 | -------------------------------------------------------------------------------- /src/injectee/injectee.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #include "hook.hpp" 17 | #include 18 | 19 | void do_client(HINSTANCE dll_handle, std::uint16_t port) { 20 | { 21 | asio::io_context io_context(1); 22 | 23 | auto qu = 24 | std::make_unique>(io_context, 1024); 25 | auto cfg = std::make_unique(); 26 | auto sock_map = std::make_unique>(); 27 | 28 | scope_ptr_bind queue_bind(queue, qu.get()); 29 | scope_ptr_bind config_bind(config, cfg.get()); 30 | scope_ptr_bind map_bind(nbio_map, sock_map.get()); 31 | 32 | injectee_client c(io_context, tcp::endpoint(localhost, port), *queue, 33 | *config); 34 | asio::co_spawn(io_context, c.start(), asio::detached); 35 | 36 | io_context.run(); 37 | } 38 | FreeLibrary(dll_handle); 39 | } 40 | 41 | std::uint16_t get_port() { 42 | handle mapping = open_mapping(get_port_mapping_name(GetCurrentProcessId())); 43 | 44 | mapped_buffer port_buf(mapping.get()); 45 | return *(std::uint16_t *)port_buf.get(); 46 | } 47 | 48 | BOOL WINAPI DllMain(HINSTANCE dll_handle, DWORD reason, LPVOID reserved) { 49 | switch (reason) { 50 | case DLL_PROCESS_ATTACH: 51 | DisableThreadLibraryCalls(dll_handle); 52 | minhook::init(); 53 | 54 | hook_create_all(); 55 | 56 | minhook::enable(); 57 | std::thread(do_client, dll_handle, get_port()).detach(); 58 | break; 59 | 60 | case DLL_PROCESS_DETACH: 61 | minhook::disable(); 62 | minhook::deinit(); 63 | break; 64 | } 65 | return TRUE; 66 | } 67 | -------------------------------------------------------------------------------- /src/injectee/services.inc: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | X("echo", 7, TCP) 17 | X("echo", 7, UDP) 18 | X("discard", 9, TCP) 19 | X("discard", 9, UDP) 20 | X("systat", 11, TCP) 21 | X("systat", 11, UDP) 22 | X("daytime", 13, TCP) 23 | X("daytime", 13, UDP) 24 | X("qotd", 17, TCP) 25 | X("qotd", 17, UDP) 26 | X("chargen", 19, TCP) 27 | X("chargen", 19, UDP) 28 | X("ftp-data", 20, TCP) 29 | X("ftp", 21, TCP) 30 | X("ssh", 22, TCP) 31 | X("telnet", 23, TCP) 32 | X("smtp", 25, TCP) 33 | X("time", 37, TCP) 34 | X("time", 37, UDP) 35 | X("rlp", 39, UDP) 36 | X("nameserver", 42, TCP) 37 | X("nameserver", 42, UDP) 38 | X("nicname", 43, TCP) 39 | X("domain", 53, TCP) 40 | X("domain", 53, UDP) 41 | X("bootps", 67, UDP) 42 | X("bootpc", 68, UDP) 43 | X("tftp", 69, UDP) 44 | X("gopher", 70, TCP) 45 | X("finger", 79, TCP) 46 | X("http", 80, TCP) 47 | X("hosts2-ns", 81, TCP) 48 | X("hosts2-ns", 81, UDP) 49 | X("kerberos", 88, TCP) 50 | X("kerberos", 88, UDP) 51 | X("hostname", 101, TCP) 52 | X("iso-tsap", 102, TCP) 53 | X("rtelnet", 107, TCP) 54 | X("pop2", 109, TCP) 55 | X("pop3", 110, TCP) 56 | X("sunrpc", 111, TCP) 57 | X("sunrpc", 111, UDP) 58 | X("auth", 113, TCP) 59 | X("uucp-path", 117, TCP) 60 | X("sqlserv", 118, TCP) 61 | X("nntp", 119, TCP) 62 | X("ntp", 123, UDP) 63 | X("epmap", 135, TCP) 64 | X("epmap", 135, UDP) 65 | X("netbios-ns", 137, TCP) 66 | X("netbios-ns", 137, UDP) 67 | X("netbios-dgm", 138, UDP) 68 | X("netbios-ssn", 139, TCP) 69 | X("imap", 143, TCP) 70 | X("sql-net", 150, TCP) 71 | X("sqlsrv", 156, TCP) 72 | X("pcmail-srv", 158, TCP) 73 | X("snmp", 161, UDP) 74 | X("snmptrap", 162, UDP) 75 | X("print-srv", 170, TCP) 76 | X("bgp", 179, TCP) 77 | X("irc", 194, TCP) 78 | X("ipx", 213, UDP) 79 | X("rtsps", 322, TCP) 80 | X("rtsps", 322, UDP) 81 | X("mftp", 349, TCP) 82 | X("mftp", 349, UDP) 83 | X("ldap", 389, TCP) 84 | X("https", 443, TCP) 85 | X("https", 443, UDP) 86 | X("microsoft-ds", 445, TCP) 87 | X("microsoft-ds", 445, UDP) 88 | X("kpasswd", 464, TCP) 89 | X("kpasswd", 464, UDP) 90 | X("isakmp", 500, UDP) 91 | X("crs", 507, TCP) 92 | X("crs", 507, UDP) 93 | X("exec", 512, TCP) 94 | X("biff", 512, UDP) 95 | X("login", 513, TCP) 96 | X("who", 513, UDP) 97 | X("cmd", 514, TCP) 98 | X("syslog", 514, UDP) 99 | X("printer", 515, TCP) 100 | X("talk", 517, UDP) 101 | X("ntalk", 518, UDP) 102 | X("efs", 520, TCP) 103 | X("router", 520, UDP) 104 | X("ulp", 522, TCP) 105 | X("ulp", 522, UDP) 106 | X("timed", 525, UDP) 107 | X("tempo", 526, TCP) 108 | X("irc-serv", 529, TCP) 109 | X("irc-serv", 529, UDP) 110 | X("courier", 530, TCP) 111 | X("conference", 531, TCP) 112 | X("netnews", 532, TCP) 113 | X("netwall", 533, UDP) 114 | X("uucp", 540, TCP) 115 | X("klogin", 543, TCP) 116 | X("kshell", 544, TCP) 117 | X("dhcpv6-client", 546, TCP) 118 | X("dhcpv6-client", 546, UDP) 119 | X("dhcpv6-server", 547, TCP) 120 | X("dhcpv6-server", 547, UDP) 121 | X("afpovertcp", 548, TCP) 122 | X("afpovertcp", 548, UDP) 123 | X("new-rwho", 550, UDP) 124 | X("rtsp", 554, TCP) 125 | X("rtsp", 554, UDP) 126 | X("remotefs", 556, TCP) 127 | X("rmonitor", 560, UDP) 128 | X("monitor", 561, UDP) 129 | X("nntps", 563, TCP) 130 | X("nntps", 563, UDP) 131 | X("whoami", 565, TCP) 132 | X("whoami", 565, UDP) 133 | X("ms-shuttle", 568, TCP) 134 | X("ms-shuttle", 568, UDP) 135 | X("ms-rome", 569, TCP) 136 | X("ms-rome", 569, UDP) 137 | X("http-rpc-epmap", 593, TCP) 138 | X("http-rpc-epmap", 593, UDP) 139 | X("hmmp-ind", 612, TCP) 140 | X("hmmp-ind", 612, UDP) 141 | X("hmmp-op", 613, TCP) 142 | X("hmmp-op", 613, UDP) 143 | X("ldaps", 636, TCP) 144 | X("doom", 666, TCP) 145 | X("doom", 666, UDP) 146 | X("msexch-routing", 691, TCP) 147 | X("msexch-routing", 691, UDP) 148 | X("kerberos-adm", 749, TCP) 149 | X("kerberos-adm", 749, UDP) 150 | X("kerberos-iv", 750, UDP) 151 | X("mdbs_daemon", 800, TCP) 152 | X("mdbs_daemon", 800, UDP) 153 | X("ftps-data", 989, TCP) 154 | X("ftps", 990, TCP) 155 | X("telnets", 992, TCP) 156 | X("imaps", 993, TCP) 157 | X("ircs", 994, TCP) 158 | X("pop3s", 995, TCP) 159 | X("pop3s", 995, UDP) 160 | X("kpop", 1109, TCP) 161 | X("nfsd-status", 1110, TCP) 162 | X("nfsd-keepalive", 1110, UDP) 163 | X("nfa", 1155, TCP) 164 | X("nfa", 1155, UDP) 165 | X("activesync", 1034, TCP) 166 | X("phone", 1167, UDP) 167 | X("opsmgr", 1270, TCP) 168 | X("opsmgr", 1270, UDP) 169 | X("ms-sql-s", 1433, TCP) 170 | X("ms-sql-s", 1433, UDP) 171 | X("ms-sql-m", 1434, TCP) 172 | X("ms-sql-m", 1434, UDP) 173 | X("ms-sna-server", 1477, TCP) 174 | X("ms-sna-server", 1477, UDP) 175 | X("ms-sna-base", 1478, TCP) 176 | X("ms-sna-base", 1478, UDP) 177 | X("wins", 1512, TCP) 178 | X("wins", 1512, UDP) 179 | X("ingreslock", 1524, TCP) 180 | X("stt", 1607, TCP) 181 | X("stt", 1607, UDP) 182 | X("l2tp", 1701, UDP) 183 | X("pptconference", 1711, TCP) 184 | X("pptconference", 1711, UDP) 185 | X("pptp", 1723, TCP) 186 | X("msiccp", 1731, TCP) 187 | X("msiccp", 1731, UDP) 188 | X("remote-winsock", 1745, TCP) 189 | X("remote-winsock", 1745, UDP) 190 | X("ms-streaming", 1755, TCP) 191 | X("ms-streaming", 1755, UDP) 192 | X("msmq", 1801, TCP) 193 | X("msmq", 1801, UDP) 194 | X("radius", 1812, UDP) 195 | X("radacct", 1813, UDP) 196 | X("msnp", 1863, TCP) 197 | X("msnp", 1863, UDP) 198 | X("ssdp", 1900, TCP) 199 | X("ssdp", 1900, UDP) 200 | X("close-combat", 1944, TCP) 201 | X("close-combat", 1944, UDP) 202 | X("nfsd", 2049, UDP) 203 | X("knetd", 2053, TCP) 204 | X("mzap", 2106, TCP) 205 | X("mzap", 2106, UDP) 206 | X("qwave", 2177, TCP) 207 | X("qwave", 2177, UDP) 208 | X("directplay", 2234, TCP) 209 | X("directplay", 2234, UDP) 210 | X("ms-olap3", 2382, TCP) 211 | X("ms-olap3", 2382, UDP) 212 | X("ms-olap4", 2383, TCP) 213 | X("ms-olap4", 2383, UDP) 214 | X("ms-olap1", 2393, TCP) 215 | X("ms-olap1", 2393, UDP) 216 | X("ms-olap2", 2394, TCP) 217 | X("ms-olap2", 2394, UDP) 218 | X("ms-theater", 2460, TCP) 219 | X("ms-theater", 2460, UDP) 220 | X("wlbs", 2504, TCP) 221 | X("wlbs", 2504, UDP) 222 | X("ms-v-worlds", 2525, TCP) 223 | X("ms-v-worlds", 2525, UDP) 224 | X("sms-rcinfo", 2701, TCP) 225 | X("sms-rcinfo", 2701, UDP) 226 | X("sms-xfer", 2702, TCP) 227 | X("sms-xfer", 2702, UDP) 228 | X("sms-chat", 2703, TCP) 229 | X("sms-chat", 2703, UDP) 230 | X("sms-remctrl", 2704, TCP) 231 | X("sms-remctrl", 2704, UDP) 232 | X("msolap-ptp2", 2725, TCP) 233 | X("msolap-ptp2", 2725, UDP) 234 | X("icslap", 2869, TCP) 235 | X("icslap", 2869, UDP) 236 | X("cifs", 3020, TCP) 237 | X("cifs", 3020, UDP) 238 | X("xbox", 3074, TCP) 239 | X("xbox", 3074, UDP) 240 | X("ms-dotnetster", 3126, TCP) 241 | X("ms-dotnetster", 3126, UDP) 242 | X("ms-rule-engine", 3132, TCP) 243 | X("ms-rule-engine", 3132, UDP) 244 | X("msft-gc", 3268, TCP) 245 | X("msft-gc", 3268, UDP) 246 | X("msft-gc-ssl", 3269, TCP) 247 | X("msft-gc-ssl", 3269, UDP) 248 | X("ms-cluster-net", 3343, TCP) 249 | X("ms-cluster-net", 3343, UDP) 250 | X("ms-wbt-server", 3389, TCP) 251 | X("ms-wbt-server", 3389, UDP) 252 | X("ms-la", 3535, TCP) 253 | X("ms-la", 3535, UDP) 254 | X("pnrp-port", 3540, TCP) 255 | X("pnrp-port", 3540, UDP) 256 | X("teredo", 3544, TCP) 257 | X("teredo", 3544, UDP) 258 | X("p2pgroup", 3587, TCP) 259 | X("p2pgroup", 3587, UDP) 260 | X("ws-discovery", 3702, UDP) 261 | X("ws-discovery", 3702, TCP) 262 | X("dvcprov-port", 3776, TCP) 263 | X("dvcprov-port", 3776, UDP) 264 | X("msfw-control", 3847, TCP) 265 | X("msdts1", 3882, TCP) 266 | X("sdp-portmapper", 3935, TCP) 267 | X("sdp-portmapper", 3935, UDP) 268 | X("net-device", 4350, TCP) 269 | X("net-device", 4350, UDP) 270 | X("ipsec-msft", 4500, TCP) 271 | X("ipsec-msft", 4500, UDP) 272 | X("llmnr", 5355, TCP) 273 | X("llmnr", 5355, UDP) 274 | X("wsd", 5357, TCP) 275 | X("wsd", 5358, TCP) 276 | X("rrac", 5678, TCP) 277 | X("rrac", 5678, UDP) 278 | X("dccm", 5679, TCP) 279 | X("dccm", 5679, UDP) 280 | X("ms-licensing", 5720, TCP) 281 | X("ms-licensing", 5720, UDP) 282 | X("directplay8", 6073, TCP) 283 | X("directplay8", 6073, UDP) 284 | X("ms-do", 7680, TCP) 285 | X("ms-do", 7680, UDP) 286 | X("man", 9535, TCP) 287 | X("rasadv", 9753, TCP) 288 | X("rasadv", 9753, UDP) 289 | X("imip-channels", 11320, TCP) 290 | X("imip-channels", 11320, UDP) 291 | X("directplaysrvr", 47624, TCP) 292 | X("directplaysrvr", 47624, UDP) 293 | -------------------------------------------------------------------------------- /src/injectee/socks5.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | constexpr const char SOCKS_VERSION = 5; 21 | constexpr const char SOCKS_NO_AUTHENTICATION = 0; 22 | 23 | constexpr const char SOCKS_CONNECT = 1; 24 | constexpr const char SOCKS_IPV4 = 1; 25 | constexpr const char SOCKS_DOMAINNAME = 3; 26 | constexpr const char SOCKS_IPV6 = 4; 27 | 28 | constexpr const char SOCKS_SUCCESS = 0; 29 | constexpr const char SOCKS_GENERAL_FAILURE = 4; 30 | 31 | constexpr const size_t SOCKS_REQUEST_MAX_SIZE = 134; 32 | 33 | bool socks5_handshake(SOCKET s) { 34 | const char req[] = {SOCKS_VERSION, 1, SOCKS_NO_AUTHENTICATION}; 35 | if (send(s, req, sizeof(req), 0) != sizeof(req)) 36 | return false; 37 | 38 | char res[2]; 39 | if (recv(s, res, sizeof(res), MSG_WAITALL) != sizeof(res)) 40 | return false; 41 | 42 | return res[0] == SOCKS_VERSION && res[1] == SOCKS_NO_AUTHENTICATION; 43 | } 44 | 45 | char socks5_request_send(SOCKET s, char *buf, size_t size) { 46 | if (send(s, buf, size, 0) != size) 47 | return SOCKS_GENERAL_FAILURE; 48 | 49 | if (recv(s, buf, 4, 0) == SOCKET_ERROR) 50 | return SOCKS_GENERAL_FAILURE; 51 | 52 | if (buf[1] != SOCKS_SUCCESS) 53 | return buf[1]; 54 | 55 | if (buf[3] == SOCKS_IPV4) { 56 | if (recv(s, buf + 4, 6, MSG_WAITALL) == SOCKET_ERROR) 57 | return SOCKS_GENERAL_FAILURE; 58 | } else if (buf[3] == SOCKS_IPV6) { 59 | if (recv(s, buf + 4, 18, MSG_WAITALL) == SOCKET_ERROR) 60 | return SOCKS_GENERAL_FAILURE; 61 | } else { 62 | return SOCKS_GENERAL_FAILURE; 63 | } 64 | 65 | return SOCKS_SUCCESS; 66 | } 67 | 68 | char socks5_request(SOCKET s, const sockaddr *addr) { 69 | char buf[SOCKS_REQUEST_MAX_SIZE] = {SOCKS_VERSION, SOCKS_CONNECT, 0}; 70 | 71 | char *ptr = buf + 3; 72 | if (addr->sa_family == AF_INET) { 73 | auto v4 = (const sockaddr_in *)addr; 74 | *ptr++ = SOCKS_IPV4; 75 | *((ULONG *&)ptr)++ = v4->sin_addr.s_addr; 76 | *((USHORT *&)ptr)++ = v4->sin_port; 77 | } else if (addr->sa_family == AF_INET6) { 78 | auto v6 = (const sockaddr_in6 *)addr; 79 | *ptr++ = SOCKS_IPV6; 80 | ptr = std::copy((const char *)&v6->sin6_addr, 81 | (const char *)(&v6->sin6_addr + 1), ptr); 82 | *((USHORT *&)ptr)++ = v6->sin6_port; 83 | } else { 84 | return SOCKS_GENERAL_FAILURE; 85 | } 86 | 87 | return socks5_request_send(s, buf, ptr - buf); 88 | } 89 | 90 | char socks5_request(SOCKET s, const IpAddr &addr) { 91 | char buf[SOCKS_REQUEST_MAX_SIZE] = {SOCKS_VERSION, SOCKS_CONNECT, 0}; 92 | 93 | char *ptr = buf + 3; 94 | if (auto v4 = addr["v4_addr"_f]) { 95 | *ptr++ = SOCKS_IPV4; 96 | *((ULONG *&)ptr)++ = *v4; 97 | } else if (auto v6 = addr["v6_addr"_f]) { 98 | *ptr++ = SOCKS_IPV6; 99 | ptr = std::copy(v6->begin(), v6->end(), ptr); 100 | } else if (auto domain = addr["domain"_f]) { 101 | *ptr++ = SOCKS_DOMAINNAME; 102 | if (domain->size() >= 256) { 103 | return SOCKS_GENERAL_FAILURE; 104 | } 105 | *ptr++ = (char)domain->size(); 106 | ptr = std::copy(domain->begin(), domain->end(), ptr); 107 | } else { 108 | return SOCKS_GENERAL_FAILURE; 109 | } 110 | *((USHORT *&)ptr)++ = *addr["port"_f]; 111 | 112 | return socks5_request_send(s, buf, ptr - buf); 113 | } 114 | -------------------------------------------------------------------------------- /src/injectee/winnet.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_INJECTEE_WINNET 17 | #define PROXINJECT_INJECTEE_WINNET 18 | 19 | #include "winraii.hpp" 20 | #include 21 | 22 | std::optional to_ip_addr(const sockaddr *name) { 23 | char buf[1024]; 24 | 25 | if (name->sa_family == AF_INET) { 26 | auto v4 = (const sockaddr_in *)name; 27 | return IpAddr((std::uint32_t)ntohl(v4->sin_addr.s_addr), {}, {}, 28 | ntohs(v4->sin_port)); 29 | } else if (name->sa_family == AF_INET6) { 30 | auto v6 = (const sockaddr_in6 *)name; 31 | auto addr = std::bit_cast>(v6->sin6_addr); 32 | return IpAddr(std::nullopt, 33 | std::vector{addr.rbegin(), addr.rend()}, {}, 34 | ntohs(v6->sin6_port)); 35 | } 36 | 37 | return std::nullopt; 38 | } 39 | 40 | std::pair, size_t> to_sockaddr(const IpAddr &addr) { 41 | if (auto v = addr["v4_addr"_f]) { 42 | auto res = new sockaddr_in(); 43 | res->sin_family = AF_INET; 44 | res->sin_addr.s_addr = htonl(v.value()); 45 | res->sin_port = htons(addr["port"_f].value()); 46 | return {std::unique_ptr{(sockaddr *)res}, sizeof(sockaddr_in)}; 47 | } else { 48 | auto res = new sockaddr_in6(); 49 | res->sin6_family = AF_INET6; 50 | std::array arr; 51 | std::copy(addr["v6_addr"_f].value().begin(), 52 | addr["v6_addr"_f].value().end(), arr.rbegin()); 53 | res->sin6_addr = *(IN6_ADDR *)&arr; 54 | res->sin6_port = htons(addr["port"_f].value()); 55 | return {std::unique_ptr{(sockaddr *)res}, sizeof(sockaddr_in6)}; 56 | } 57 | 58 | return {nullptr, 0}; 59 | } 60 | 61 | bool is_localhost(const sockaddr *name) { 62 | if (name->sa_family == AF_INET) { 63 | auto v4 = (const sockaddr_in *)name; 64 | return v4->sin_addr.s_net == 0x7f; 65 | } else if (name->sa_family == AF_INET6) { 66 | auto v6 = (const sockaddr_in6 *)name; 67 | return v6->sin6_addr.u.Word[0] == 0 && v6->sin6_addr.u.Word[1] == 0 && 68 | v6->sin6_addr.u.Word[2] == 0 && v6->sin6_addr.u.Word[3] == 0 && 69 | v6->sin6_addr.u.Word[4] == 0 && v6->sin6_addr.u.Word[5] == 0 && 70 | v6->sin6_addr.u.Word[6] == 0 && v6->sin6_addr.u.Byte[14] == 0 && 71 | v6->sin6_addr.u.Byte[15] == 1; 72 | } 73 | 74 | return false; 75 | } 76 | 77 | bool is_inet(const sockaddr *name) { 78 | return name->sa_family == AF_INET || name->sa_family == AF_INET6; 79 | } 80 | 81 | bool sockequal(const sockaddr *l, const sockaddr *r) { 82 | if (l->sa_family == r->sa_family) { 83 | if (l->sa_family == AF_INET) { 84 | auto l4 = (const sockaddr_in *)l; 85 | auto r4 = (const sockaddr_in *)r; 86 | 87 | return l4->sin_addr.S_un.S_addr == r4->sin_addr.S_un.S_addr && 88 | l4->sin_port == r4->sin_port; 89 | } else if (l->sa_family == AF_INET6) { 90 | auto l6 = (const sockaddr_in6 *)l; 91 | auto r6 = (const sockaddr_in6 *)r; 92 | 93 | return l6->sin6_port == r6->sin6_port && 94 | std::equal(l6->sin6_addr.u.Byte, l6->sin6_addr.u.Byte + 16, 95 | r6->sin6_addr.u.Byte); 96 | } 97 | 98 | return false; // FIXME: add equal checking for more family 99 | } 100 | 101 | return false; 102 | } 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /src/injector/injector.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_INJECTOR_INJECTOR 17 | #define PROXINJECT_INJECTOR_INJECTOR 18 | 19 | #include "utils.hpp" 20 | #include "winraii.hpp" 21 | #include 22 | 23 | namespace fs = std::filesystem; 24 | 25 | struct injector { 26 | static inline const FARPROC load_library = 27 | GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryW"); 28 | 29 | #if defined(_WIN64) 30 | static inline const char wow64_address_dumper_filename[] = 31 | "wow64-address-dumper.exe"; 32 | 33 | static std::optional get_wow64_load_library() { 34 | auto path = fs::path(get_current_filename()) 35 | .replace_filename(wow64_address_dumper_filename); 36 | 37 | if (!std::filesystem::exists(path)) { 38 | return std::nullopt; 39 | } 40 | 41 | auto pi = create_process(path.wstring(), CREATE_NO_WINDOW); 42 | if (!pi) { 43 | return std::nullopt; 44 | } 45 | 46 | WaitForSingleObject(pi->hProcess, INFINITE); 47 | 48 | DWORD ec = 0; 49 | if (!GetExitCodeProcess(pi->hProcess, &ec)) { 50 | return std::nullopt; 51 | } 52 | 53 | return reinterpret_cast((uint64_t)ec); 54 | } 55 | 56 | static inline const FARPROC load_library_wow64 = 57 | get_wow64_load_library().value_or(nullptr); 58 | #endif 59 | 60 | static bool inject(DWORD pid, HANDLE proc, std::uint16_t port, BOOL isWoW64, 61 | std::wstring_view filename) { 62 | handle mapping = 63 | create_mapping(get_port_mapping_name(pid), sizeof(std::uint16_t)); 64 | if (!mapping) { 65 | return false; 66 | } 67 | 68 | mapped_buffer port_buf(mapping.get()); 69 | *(std::uint16_t *)port_buf.get() = port; 70 | 71 | virtual_memory mem(proc, (filename.size() + 1) * sizeof(wchar_t)); 72 | if (!mem) 73 | return false; 74 | 75 | if (!mem.write(filename.data())) 76 | return false; 77 | 78 | FARPROC current_load_library = 79 | #if defined(_WIN64) 80 | isWoW64 ? load_library_wow64 : load_library; 81 | #else 82 | load_library; 83 | #endif 84 | if (!current_load_library) { 85 | return false; 86 | } 87 | 88 | handle thread = CreateRemoteThread( 89 | proc, nullptr, 0, (LPTHREAD_START_ROUTINE)current_load_library, 90 | mem.get(), 0, nullptr); 91 | if (!thread) 92 | return false; 93 | 94 | WaitForSingleObject(thread.get(), INFINITE); 95 | 96 | return true; 97 | } 98 | 99 | static inline const char injectee_filename[] = "proxinjectee.dll"; 100 | static inline const char injectee_wow64_filename[] = "proxinjectee32.dll"; 101 | 102 | static std::optional 103 | find_injectee(std::wstring_view self_binary_path, BOOL isWoW64) { 104 | auto path = fs::path(self_binary_path) 105 | .replace_filename(isWoW64 ? injectee_wow64_filename 106 | : injectee_filename); 107 | 108 | if (!std::filesystem::exists(path)) { 109 | return std::nullopt; 110 | } 111 | 112 | return path.wstring(); 113 | } 114 | 115 | static bool inject(DWORD pid, std::uint16_t port) { 116 | handle proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); 117 | if (!proc) 118 | return false; 119 | 120 | BOOL isWoW64 = false; 121 | #if defined(_WIN64) 122 | if (!IsWow64Process(proc.get(), &isWoW64)) { 123 | return false; 124 | } 125 | #endif 126 | 127 | if (auto path = find_injectee(get_current_filename(), isWoW64)) { 128 | return inject(pid, proc.get(), port, isWoW64, path.value()); 129 | } 130 | 131 | return false; 132 | } 133 | 134 | template 135 | static void pid_by_name_wildcard(const std::string &name, F &&f) { 136 | match_process_by_name([name, &f](const std::string &pname, DWORD pid) { 137 | if (filename_wildcard_match(name.data(), pname.data())) { 138 | std::forward(f)(pid); 139 | } 140 | }); 141 | } 142 | 143 | template 144 | static void pid_by_name_regex(const std::string &name, F &&f) { 145 | match_process_by_name([name, &f](const std::string &pname, DWORD pid) { 146 | if (regex_match_filename(name, pname)) { 147 | std::forward(f)(pid); 148 | } 149 | }); 150 | } 151 | 152 | template 153 | static void pid_by_path_wildcard(const std::string &path, F &&f) { 154 | match_process_by_path([path, &f](const std::string &ppath, DWORD pid) { 155 | if (filename_wildcard_match(path.data(), ppath.data())) { 156 | std::forward(f)(pid); 157 | } 158 | }); 159 | } 160 | 161 | template 162 | static void pid_by_path_regex(const std::string &path, F &&f) { 163 | match_process_by_path([path, &f](const std::string &ppath, DWORD pid) { 164 | if (regex_match_filename(path, ppath)) { 165 | std::forward(f)(pid); 166 | } 167 | }); 168 | } 169 | }; 170 | 171 | #endif 172 | -------------------------------------------------------------------------------- /src/injector/injector_cli.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #include 17 | 18 | #include "injector.hpp" 19 | #include "injector_cli.hpp" 20 | #include "utils.hpp" 21 | #include "version.hpp" 22 | #include 23 | #include 24 | 25 | using argparse::ArgumentParser; 26 | using namespace std; 27 | 28 | auto create_parser() { 29 | ArgumentParser parser("proxinjector-cli", proxinject_version, 30 | argparse::default_arguments::help); 31 | 32 | parser.add_description(proxinject_description); 33 | 34 | parser.add_argument("-v", "--version") 35 | .action([&](const auto & /*unused*/) { 36 | std::cout << proxinject_copyright(proxinject_version) << std::endl; 37 | std::exit(0); 38 | }) 39 | .default_value(false) 40 | .help("prints version information and exits") 41 | .implicit_value(true) 42 | .nargs(0); 43 | 44 | parser.add_argument("-i", "--pid") 45 | .help("pid of a process to inject proxy (integer)") 46 | .default_value(vector{}) 47 | .scan<'d', int>() 48 | .append(); 49 | 50 | parser.add_argument("-n", "--name") 51 | .help("short filename of a process with wildcard matching to inject " 52 | "proxy (string, without directory and file extension, e.g. " 53 | "`python`, `py*`, `py??on`)") 54 | .default_value(vector{}) 55 | .append(); 56 | 57 | parser.add_argument("-P", "--path") 58 | .help("full filename of a process with wildcard matching to inject proxy " 59 | "(string, with directory and file extension, e.g. " 60 | "`C:/programs/python.exe`, `C:/programs/*.exe`)") 61 | .default_value(vector{}) 62 | .append(); 63 | 64 | parser.add_argument("-r", "--name-regexp") 65 | .help("regular expression for short filename of a process " 66 | "to inject proxy (string, without directory and file " 67 | "extension, e.g. `python`, `py.*|exp.*`)") 68 | .default_value(vector{}) 69 | .append(); 70 | 71 | parser.add_argument("-R", "--path-regexp") 72 | .help("regular expression for full filename of a process " 73 | "to inject proxy (string, with directory and file " 74 | "extension, e.g. `C:/programs/python.exe`, " 75 | "`C:/programs/(a|b).*\\.exe`)") 76 | .default_value(vector{}) 77 | .append(); 78 | 79 | parser.add_argument("-e", "--exec") 80 | .help("command line started with an executable to create a new process " 81 | "and inject proxy (string, e.g. `python` or `C:\\Program " 82 | "Files\\a.exe --some-option`)") 83 | .default_value(vector{}) 84 | .append(); 85 | 86 | parser.add_argument("-l", "--enable-log") 87 | .help("enable logging for network connections") 88 | .default_value(false) 89 | .implicit_value(true); 90 | 91 | parser.add_argument("-p", "--set-proxy") 92 | .help("set a proxy address for network connections (string, e.g. " 93 | "`127.0.0.1:1080`)") 94 | .default_value(string{}); 95 | 96 | parser.add_argument("-w", "--new-console-window") 97 | .help("create a new console window while a new console process is " 98 | "executed in `-e`") 99 | .default_value(false) 100 | .implicit_value(true); 101 | 102 | parser.add_argument("-s", "--subprocess") 103 | .help("inject subprocesses created by these already injected processes") 104 | .default_value(false) 105 | .implicit_value(true); 106 | 107 | return parser; 108 | } 109 | 110 | int main(int argc, char *argv[]) { 111 | auto parser = create_parser(); 112 | 113 | try { 114 | parser.parse_args(argc, argv); 115 | } catch (const runtime_error &err) { 116 | cerr << err.what() << endl; 117 | cerr << parser; 118 | return 1; 119 | } 120 | 121 | auto pids = parser.get>("-i"); 122 | auto proc_names = parser.get>("-n"); 123 | auto proc_paths = parser.get>("-P"); 124 | auto proc_re_names = parser.get>("-r"); 125 | auto proc_re_paths = parser.get>("-R"); 126 | auto create_paths = parser.get>("-e"); 127 | 128 | if (pids.empty() && proc_names.empty() && create_paths.empty()) { 129 | cerr << "Expected at least one of `-i`, `-n` or `-e`" << endl; 130 | cerr << parser; 131 | return 2; 132 | } 133 | 134 | asio::io_context io_context(1); 135 | injector_server server; 136 | 137 | auto acceptor = tcp::acceptor(io_context, auto_endpoint); 138 | server.set_port(acceptor.local_endpoint().port()); 139 | info("connection port is set to {}", server.port_); 140 | 141 | asio::co_spawn(io_context, 142 | listener(std::move(acceptor), server), 143 | asio::detached); 144 | 145 | jthread io_thread([&io_context] { io_context.run(); }); 146 | 147 | if (parser.get("-l")) { 148 | server.enable_log(); 149 | info("logging enabled"); 150 | } 151 | 152 | if (parser.get("-s")) { 153 | server.enable_subprocess(); 154 | info("subprocess injection enabled"); 155 | } 156 | 157 | if (auto proxy_str = trim_copy(parser.get("-p")); 158 | !proxy_str.empty()) { 159 | if (auto res = parse_address(proxy_str)) { 160 | auto [addr, port] = res.value(); 161 | server.set_proxy(ip::address::from_string(addr), port); 162 | info("proxy address set to {}:{}", addr, port); 163 | } 164 | } 165 | 166 | bool has_process = false; 167 | auto report_injected = [&has_process](DWORD pid) { 168 | info("{}: injected", pid); 169 | has_process = true; 170 | }; 171 | 172 | for (auto pid : pids) { 173 | if (pid > 0) { 174 | if (server.inject(pid)) { 175 | report_injected(pid); 176 | } 177 | } 178 | } 179 | 180 | for (const auto &name : proc_names) { 181 | injector::pid_by_name_wildcard(name, 182 | [&server, &report_injected](DWORD pid) { 183 | if (server.inject(pid)) { 184 | report_injected(pid); 185 | } 186 | }); 187 | } 188 | 189 | for (const auto &path : proc_paths) { 190 | injector::pid_by_path_wildcard(path, 191 | [&server, &report_injected](DWORD pid) { 192 | if (server.inject(pid)) { 193 | report_injected(pid); 194 | } 195 | }); 196 | } 197 | 198 | for (const auto &name : proc_re_names) { 199 | injector::pid_by_name_regex(name, [&server, &report_injected](DWORD pid) { 200 | if (server.inject(pid)) { 201 | report_injected(pid); 202 | } 203 | }); 204 | } 205 | 206 | for (const auto &path : proc_re_paths) { 207 | injector::pid_by_path_regex(path, [&server, &report_injected](DWORD pid) { 208 | if (server.inject(pid)) { 209 | report_injected(pid); 210 | } 211 | }); 212 | } 213 | 214 | for (const auto &file : create_paths) { 215 | DWORD creation_flags = parser.get("-w") ? CREATE_NEW_CONSOLE : 0; 216 | if (auto res = create_process(file, creation_flags)) { 217 | if (server.inject(res->dwProcessId)) { 218 | report_injected(res->dwProcessId); 219 | } 220 | } 221 | } 222 | 223 | if (!has_process) { 224 | info("no process has been injected, exit"); 225 | io_context.stop(); 226 | } 227 | 228 | io_thread.join(); 229 | } 230 | -------------------------------------------------------------------------------- /src/injector/injector_cli.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_INJECTOR_INJECTOR_CLI 17 | #define PROXINJECT_INJECTOR_INJECTOR_CLI 18 | 19 | #include "server.hpp" 20 | #include 21 | #include 22 | 23 | using spdlog::info; 24 | 25 | struct injectee_session_cli : injectee_session { 26 | using injectee_session::injectee_session; 27 | 28 | asio::awaitable process_connect(const InjecteeConnect &msg) override { 29 | if (auto v = msg["proxy"_f]) 30 | info("{}: {} {} via {}", (int)pid_, *msg["syscall"_f], *msg["addr"_f], 31 | *v); 32 | else 33 | info("{}: {} {}", (int)pid_, *msg["syscall"_f], *msg["addr"_f]); 34 | co_return; 35 | } 36 | 37 | asio::awaitable process_pid() override { 38 | info("{}: established injectee connection", (int)pid_); 39 | co_return; 40 | } 41 | 42 | void process_close() override { 43 | info("{}: closed", (int)pid_); 44 | if (server_.clients.size() == 0) { 45 | info("all processes have been exited, exit"); 46 | 47 | exit(0); 48 | } 49 | } 50 | }; 51 | 52 | std::optional> 53 | parse_address(const std::string &addr) { 54 | auto delimiter = addr.find_last_of(':'); 55 | if (delimiter == std::string::npos) { 56 | return std::nullopt; 57 | } 58 | 59 | auto host = addr.substr(0, delimiter); 60 | uint16_t port = std::stoul(addr.substr(delimiter + 1)); 61 | 62 | return make_pair(host, port); 63 | } 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/injector/injector_gui.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #include 17 | 18 | #include "injector.hpp" 19 | #include "injector_gui.hpp" 20 | #include "server.hpp" 21 | #include "utils.hpp" 22 | 23 | void do_server(injector_server &server, ce::view &view, 24 | process_vector &process_vec, auto &...elements) { 25 | asio::io_context io_context(1); 26 | 27 | auto acceptor = tcp::acceptor(io_context, auto_endpoint); 28 | server.set_port(acceptor.local_endpoint().port()); 29 | 30 | asio::co_spawn(io_context, 31 | listener(std::move(acceptor), server, 32 | view, process_vec, elements...), 33 | asio::detached); 34 | 35 | asio::signal_set signals(io_context, SIGINT, SIGTERM); 36 | signals.async_wait([&](auto, auto) { io_context.stop(); }); 37 | 38 | io_context.run(); 39 | } 40 | 41 | int main(int argc, char *argv[]) { 42 | injector_server server; 43 | process_vector process_vec; 44 | 45 | ce::app app(argc, argv, "proxinject", "proxinject"); 46 | ce::window win(app.name()); 47 | win.on_close = [&app]() { app.stop(); }; 48 | 49 | ce::view view(win); 50 | 51 | auto &&[controls, list_ptr, log_ptr] = 52 | make_controls(server, view, process_vec); 53 | view.content(controls, ce::box(bg_color)); 54 | 55 | std::thread([&] { 56 | do_server(server, view, process_vec, *list_ptr, *log_ptr); 57 | }).detach(); 58 | 59 | app.run(); 60 | } 61 | -------------------------------------------------------------------------------- /src/injector/injector_gui.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_INJECTOR_INJECTOR_GUI 17 | #define PROXINJECT_INJECTOR_INJECTOR_GUI 18 | 19 | #include "server.hpp" 20 | #include "ui_elements/dynamic_list.hpp" 21 | #include "ui_elements/text_box.hpp" 22 | #include "ui_elements/tooltip.hpp" 23 | 24 | #include "utils.hpp" 25 | #include "version.hpp" 26 | #include 27 | #include 28 | 29 | namespace ce = cycfi::elements; 30 | 31 | auto constexpr bg_color = ce::rgba(35, 35, 37, 255); 32 | 33 | constexpr auto bred = ce::colors::red.opacity(0.4); 34 | constexpr auto bblue = ce::colors::blue.opacity(0.4); 35 | constexpr auto brblue = ce::colors::royal_blue.opacity(0.4); 36 | constexpr auto bcblue = ce::colors::cornflower_blue.opacity(0.4); 37 | 38 | using process_vector = std::vector>; 39 | 40 | struct injectee_session_ui : injectee_session { 41 | injectee_session_ui(tcp::socket socket, injector_server &server, 42 | ce::view &view_, process_vector &vec_, auto &list_, 43 | auto &log_) 44 | : injectee_session(std::move(socket), server), view_(view_), vec_(vec_), 45 | list_(list_), log_(log_) {} 46 | 47 | ce::view &view_; 48 | process_vector &vec_; 49 | 50 | ce::dynamic_list_s &list_; 51 | ce::selectable_text_box &log_; 52 | 53 | asio::awaitable process_connect(const InjecteeConnect &msg) override { 54 | auto curr_time = std::chrono::system_clock::now(); 55 | auto curr_sec = std::chrono::system_clock::to_time_t(curr_time); 56 | auto curr_ms = std::chrono::duration_cast( 57 | curr_time.time_since_epoch()); 58 | auto curr_milli_part = curr_ms.count() % 1000; 59 | 60 | std::stringstream stream; 61 | stream << "[" 62 | << std::put_time(std::localtime(&curr_sec), "%Y-%m-%d %H:%M:%S") 63 | << "." << std::setfill('0') << std::setw(3) << curr_milli_part 64 | << "] "; 65 | stream << (int)pid_ << ": " << *msg["syscall"_f] << " " << *msg["addr"_f]; 66 | if (auto v = msg["proxy"_f]) 67 | stream << " via " << *v; 68 | stream << "\n"; 69 | 70 | view_.post([&log = log_, str = stream.str()] { 71 | log.set_text(log.get_text() + str); 72 | }); 73 | view_.refresh(); 74 | 75 | co_return; 76 | } 77 | 78 | asio::awaitable process_pid() override { 79 | vec_.emplace_back(pid_, get_process_name(pid_)); 80 | refresh(); 81 | co_return; 82 | } 83 | 84 | void process_close() override { 85 | auto iter = std::find_if(vec_.begin(), vec_.end(), [this](auto &&pair) { 86 | return pair.first == pid_; 87 | }); 88 | if (iter != vec_.end()) { 89 | vec_.erase(iter); 90 | refresh(); 91 | } 92 | } 93 | 94 | void refresh() { 95 | std::sort(vec_.begin(), vec_.end()); 96 | view_.post([&list = list_, &vec = vec_] { list.resize(vec.size()); }); 97 | view_.refresh(); 98 | } 99 | }; 100 | 101 | template auto make_tip_below(T &&element, const std::string &tip) { 102 | return ce::tooltip_below( 103 | std::forward(element), 104 | ce::layer(ce::margin({20, 8, 20, 8}, ce::label(tip)), ce::panel{})); 105 | } 106 | 107 | template 108 | auto make_tip_below_r(T &&element, const std::string &tip) { 109 | auto res = make_tip_below(std::forward(element), tip); 110 | 111 | res.location = [](const ce::rect &wh, const ce::rect &ctx) { 112 | return wh.move_to(ctx.right - wh.right, ctx.bottom); 113 | }; 114 | 115 | return res; 116 | } 117 | 118 | const std::vector> input_tips{ 119 | {"pid", "a process ID (e.g. `2333`)"}, 120 | {"name", 121 | "a process name with wildcard matching (e.g. `python`, `py*`, `py??on`)"}, 122 | {"name regexp", 123 | "a regular expression for process name (e.g. `python`, `py.*|firefox`)"}, 124 | {"path", "a process full path with wildcard matching (e.g. " 125 | "`C:/program.exe`, `C:/programs/*.exe`)"}, 126 | {"path regexp", "a regular expression for process full path (e.g. " 127 | "`C:/program.exe`, `C:/programs/(a|b).*`)"}, 128 | {"exec", 129 | "command line (e.g. `python`, `C:/programs/something --some-option`)"}}; 130 | 131 | const std::map 132 | input_tip_texts(input_tips.begin(), input_tips.end()); 133 | 134 | const std::vector input_tip_options = [] { 135 | std::vector result; 136 | 137 | for (const auto &[key, _] : input_tips) { 138 | result.emplace_back(key); 139 | } 140 | 141 | return result; 142 | }(); 143 | 144 | auto make_controls(injector_server &server, ce::view &view, 145 | process_vector &process_vec) { 146 | using namespace ce; 147 | 148 | auto [process_input, process_input_ptr] = 149 | input_box(std::string(input_tip_texts.at("pid"))); 150 | 151 | auto [input_select, input_select_ptr] = selection_menu( 152 | [process_input_ptr](auto str) { 153 | process_input_ptr->_placeholder = input_tip_texts.at(str); 154 | }, 155 | input_tip_options); 156 | 157 | auto inject_click = [input_select_ptr, process_input_ptr](F &&f) { 158 | auto text = trim_copy(process_input_ptr->get_text()); 159 | if (text.empty()) 160 | return; 161 | auto option = input_select_ptr->get_text(); 162 | if (option == "pid") { 163 | if (!all_of_digit(text)) 164 | return; 165 | 166 | DWORD pid = std::stoul(text); 167 | if (pid == 0) 168 | return; 169 | if (!std::forward(f)(pid)) 170 | return; 171 | } else if (option == "name") { 172 | bool success = false; 173 | injector::pid_by_name_wildcard(text, [&success, &f](DWORD pid) { 174 | if (std::forward(f)(pid)) 175 | success = true; 176 | }); 177 | if (!success) 178 | return; 179 | } else if (option == "name regexp") { 180 | bool success = false; 181 | injector::pid_by_name_regex(text, [&success, &f](DWORD pid) { 182 | if (std::forward(f)(pid)) 183 | success = true; 184 | }); 185 | if (!success) 186 | return; 187 | } else if (option == "path") { 188 | bool success = false; 189 | injector::pid_by_path_wildcard(text, [&success, &f](DWORD pid) { 190 | if (std::forward(f)(pid)) 191 | success = true; 192 | }); 193 | if (!success) 194 | return; 195 | } else if (option == "path regexp") { 196 | bool success = false; 197 | injector::pid_by_path_regex(text, [&success, &f](DWORD pid) { 198 | if (std::forward(f)(pid)) 199 | success = true; 200 | }); 201 | if (!success) 202 | return; 203 | } else if (option == "exec") { 204 | auto res = create_process(text); 205 | if (!res) { 206 | return; 207 | } 208 | 209 | if (!std::forward(f)(res->dwProcessId)) { 210 | return; 211 | } 212 | } 213 | process_input_ptr->set_text(""); 214 | }; 215 | 216 | auto inject_button = icon_button(icons::plus, 1.2, bblue); 217 | inject_button.on_click = [&server, inject_click](bool) { 218 | inject_click([&server](DWORD x) { return server.inject(x); }); 219 | }; 220 | 221 | auto remove_button = icon_button(icons::cancel, 1.2, bred); 222 | remove_button.on_click = [&server, inject_click](bool) { 223 | inject_click([&server](DWORD x) { return server.close(x); }); 224 | }; 225 | 226 | auto process_list = share(dynamic_list_s(basic_cell_composer( 227 | process_vec.size(), [&process_vec](size_t index) -> element_ptr { 228 | if (index < process_vec.size()) { 229 | auto [pid, name] = process_vec[index]; 230 | return share(align_center( 231 | htile(hsize(80, align_center(label(std::to_string(pid)))), 232 | hmin_size(120, align_center(label(name)))))); 233 | } 234 | return share(align_center(label("unknown"))); 235 | }))); 236 | 237 | auto [addr_input, addr_input_ptr] = input_box("IP address"); 238 | auto [port_input, port_input_ptr] = input_box("port"); 239 | 240 | auto proxy_toggle = share(toggle_icon_button(icons::power, 1.2, brblue)); 241 | proxy_toggle->on_click = [&server, proxy_toggle, addr_input_ptr, 242 | port_input_ptr](bool on) { 243 | if (on) { 244 | auto addr = trim_copy(addr_input_ptr->get_text()); 245 | auto port = trim_copy(port_input_ptr->get_text()); 246 | if (all_of_digit(port) && !addr.empty() && !port.empty()) { 247 | server.set_proxy(ip::address::from_string(addr), std::stoul(port)); 248 | } else { 249 | proxy_toggle->value(false); 250 | } 251 | } else { 252 | server.clear_proxy(); 253 | } 254 | }; 255 | 256 | auto log_toggle = toggle_icon_button(icons::doc, 1.2, brblue); 257 | log_toggle.on_click = [&server](bool on) { server.enable_log(on); }; 258 | 259 | auto subprocess_toggle = toggle_icon_button(icons::record, 1.2, brblue); 260 | subprocess_toggle.on_click = [&server](bool on) { 261 | server.enable_subprocess(on); 262 | }; 263 | 264 | auto log_box = share(selectable_text_box("")); 265 | 266 | auto clean_button = icon_button(icons::trash, 1.2, bcblue); 267 | clean_button.on_click = [log_box](bool) { log_box->set_text(""); }; 268 | 269 | auto info_button = icon_button(icons::info, 1.2, bcblue); 270 | info_button.on_click = [&view](bool) { 271 | view.add(message_box1(view, proxinject_copyright(proxinject_version), 272 | icons::info, [] {})); 273 | }; 274 | 275 | // clang-format off 276 | return std::make_tuple( 277 | margin({10, 10, 10, 10}, 278 | htile( 279 | hmin_size(200, 280 | vtile( 281 | htile( 282 | hsize(80, input_select), 283 | left_margin(5, hmin_size(100, process_input)), 284 | left_margin(10, make_tip_below(inject_button, "add specific processes to inject")), 285 | left_margin(5, make_tip_below(remove_button, "remove specific processes from injecting")) 286 | ), 287 | top_margin(10, 288 | vmin_size(250, 289 | layer(vscroller(hold(process_list)), frame()) 290 | ) 291 | ) 292 | ) 293 | ), 294 | left_margin(10, 295 | hmin_size(200, 296 | vtile( 297 | htile( 298 | hmin_size(100, addr_input), 299 | left_margin(5, hsize(100, port_input)), 300 | left_margin(10, make_tip_below_r(hold(proxy_toggle), "enable/disable proxy injection")), 301 | left_margin(5, make_tip_below_r(log_toggle, "enable/disable connection log")), 302 | left_margin(5, make_tip_below_r(subprocess_toggle, "enable/disable subprocess injection")), 303 | left_margin(8, make_tip_below_r(clean_button, "clean the logging box")), 304 | left_margin(5, make_tip_below_r(info_button, "software information")) 305 | ), 306 | top_margin(10, 307 | vmin_size(250, 308 | layer(vscroller(hold(log_box)), frame()) 309 | ) 310 | ) 311 | ) 312 | ) 313 | ) 314 | ) 315 | ), process_list, log_box); 316 | // clang-format on 317 | } 318 | 319 | #endif 320 | -------------------------------------------------------------------------------- /src/injector/server.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef PROXINJECT_INJECTOR_SERVER 17 | #define PROXINJECT_INJECTOR_SERVER 18 | 19 | #include "async_io.hpp" 20 | #include "injector.hpp" 21 | #include "schema.hpp" 22 | #include 23 | #include 24 | 25 | using tcp = asio::ip::tcp; 26 | 27 | struct injectee_client { 28 | virtual ~injectee_client() {} 29 | 30 | virtual void stop() = 0; 31 | virtual asio::awaitable config(const InjectorConfig &) = 0; 32 | virtual asio::any_io_executor get_context() = 0; 33 | }; 34 | 35 | using injectee_client_ptr = std::shared_ptr; 36 | 37 | struct injector_server { 38 | std::map clients; 39 | InjectorConfig config_; 40 | std::mutex config_mutex; 41 | 42 | std::uint16_t port_ = -1; 43 | 44 | void set_port(std::uint16_t port) { port_ = port; } 45 | 46 | bool inject(DWORD pid) { 47 | if (port_ == -1) { 48 | return false; 49 | } 50 | if (clients.contains(pid)) { 51 | return false; 52 | } else { 53 | return injector::inject(pid, port_); 54 | } 55 | } 56 | 57 | bool open(DWORD pid, injectee_client_ptr ptr) { 58 | return clients.emplace(pid, ptr).second; 59 | } 60 | 61 | void broadcast_config() { 62 | for (const auto &[_, client] : clients) { 63 | asio::co_spawn( 64 | client->get_context(), 65 | [cfg = config_, client] { return client->config(cfg); }, 66 | asio::detached); 67 | } 68 | } 69 | 70 | template void config_proxy(T &&v) { 71 | std::lock_guard guard(config_mutex); 72 | config_["addr"_f] = std::forward(v); 73 | 74 | broadcast_config(); 75 | } 76 | 77 | void set_proxy(const ip::address &addr, std::uint32_t port) { 78 | config_proxy(from_asio(addr, port)); 79 | } 80 | 81 | void clear_proxy() { config_proxy(std::nullopt); } 82 | 83 | void enable_log(bool enable = true) { 84 | std::lock_guard guard(config_mutex); 85 | config_["log"_f] = enable; 86 | 87 | broadcast_config(); 88 | } 89 | 90 | void disable_log() { enable_log(false); } 91 | 92 | void enable_subprocess(bool enable = true) { 93 | std::lock_guard guard(config_mutex); 94 | config_["subprocess"_f] = enable; 95 | 96 | broadcast_config(); 97 | } 98 | 99 | void disable_subprocess() { enable_subprocess(false); } 100 | 101 | InjectorConfig get_config() { 102 | std::lock_guard guard(config_mutex); 103 | return config_; 104 | } 105 | 106 | bool remove(DWORD pid) { 107 | if (auto iter = clients.find(pid); iter != clients.end()) { 108 | clients.erase(iter); 109 | return true; 110 | } 111 | 112 | return false; 113 | } 114 | 115 | bool close(DWORD pid) { 116 | if (auto iter = clients.find(pid); iter != clients.end()) { 117 | iter->second->stop(); 118 | return true; 119 | } 120 | 121 | return false; 122 | } 123 | }; 124 | 125 | struct injectee_session : injectee_client, 126 | std::enable_shared_from_this { 127 | tcp::socket socket_; 128 | asio::steady_timer timer_; 129 | injector_server &server_; 130 | DWORD pid_; 131 | 132 | injectee_session(tcp::socket socket, injector_server &server) 133 | : socket_(std::move(socket)), timer_(socket_.get_executor()), 134 | server_(server), pid_(0) { 135 | timer_.expires_at(std::chrono::steady_clock::time_point::max()); 136 | } 137 | 138 | void start() { 139 | asio::co_spawn( 140 | socket_.get_executor(), 141 | [self = shared_from_this()] { return self->reader(); }, asio::detached); 142 | } 143 | 144 | asio::any_io_executor get_context() { return socket_.get_executor(); } 145 | 146 | asio::awaitable config(const InjectorConfig &cfg) { 147 | co_await async_write_message( 148 | socket_, create_message(cfg)); 149 | } 150 | 151 | asio::awaitable reader() { 152 | try { 153 | while (true) { 154 | auto msg = co_await async_read_message(socket_); 155 | asio::co_spawn( 156 | socket_.get_executor(), 157 | [self = shared_from_this(), msg = std::move(msg)] { 158 | return self->process(msg); 159 | }, 160 | asio::detached); 161 | } 162 | } catch (std::exception &) { 163 | stop(); 164 | } 165 | } 166 | 167 | virtual asio::awaitable process_pid() { co_return; } 168 | virtual asio::awaitable process_connect(const InjecteeConnect &msg) { 169 | co_return; 170 | } 171 | virtual asio::awaitable process_subpid(std::uint16_t pid, bool result) { 172 | co_return; 173 | } 174 | virtual void process_close() {} 175 | 176 | asio::awaitable process(const InjecteeMessage &msg) { 177 | if (auto v = compare_message<"pid">(msg)) { 178 | pid_ = *v; 179 | server_.open(pid_, shared_from_this()); 180 | auto config_ = server_.get_config(); 181 | if (config_["subprocess"_f] && *config_["subprocess"_f]) { 182 | enumerate_child_pids(pid_, [this](DWORD pid) { server_.inject(pid); }); 183 | } 184 | co_await config(config_); 185 | co_await process_pid(); 186 | } else if (auto v = compare_message<"connect">(msg)) { 187 | co_await process_connect(*v); 188 | } else if (auto v = compare_message<"subpid">(msg)) { 189 | co_await process_subpid(*v, server_.inject(*v)); 190 | } 191 | } 192 | 193 | void stop() { 194 | socket_.close(); 195 | timer_.cancel(); 196 | server_.remove(pid_); 197 | process_close(); 198 | } 199 | }; 200 | 201 | template 202 | asio::awaitable listener(tcp::acceptor acceptor, auto &&...args) { 203 | for (;;) { 204 | std::make_shared( 205 | co_await acceptor.async_accept(asio::use_awaitable), 206 | std::forward(args)...) 207 | ->start(); 208 | } 209 | } 210 | 211 | #endif 212 | -------------------------------------------------------------------------------- /src/injector/ui_elements/dynamic_list.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | /*============================================================================= 17 | Copyright (c) 2016-2020 Joel de Guzman 18 | 19 | Distributed under the MIT License [ https://opensource.org/licenses/MIT ] 20 | =============================================================================*/ 21 | 22 | #include "dynamic_list.hpp" 23 | 24 | namespace cycfi { 25 | namespace elements { 26 | 27 | view_limits dynamic_list_s::limits(basic_context const &ctx) const { 28 | if (_composer) { 29 | if (_update_request) { 30 | update(ctx); 31 | } 32 | auto secondary_limits = _composer->secondary_axis_limits(ctx); 33 | if (_composer->size()) { 34 | return {{secondary_limits.min, float(_main_axis_full_size)}, 35 | {secondary_limits.max, full_extent}}; 36 | } 37 | } 38 | return {{0, 0}, {full_extent, full_extent}}; 39 | } 40 | 41 | void dynamic_list_s::draw(context const &ctx) { 42 | // Johann Philippe : this seems to be necessary for context where a 43 | // hdynamic_list is inside vdynamic_list (2D tables) 44 | if (_update_request) 45 | update(ctx); 46 | 47 | auto &cnv = ctx.canvas; 48 | auto state = cnv.new_state(); 49 | auto clip_extent = cnv.clip_extent(); 50 | auto main_axis_start = get_main_axis_start(ctx.bounds); 51 | 52 | if (!intersects(ctx.bounds, clip_extent)) 53 | return; 54 | 55 | auto it = std::lower_bound(_cells.begin(), _cells.end(), 56 | get_main_axis_start(clip_extent) - main_axis_start, 57 | [](auto const &cell, double pivot) { 58 | return (cell.pos + cell.main_axis_size) < pivot; 59 | }); 60 | 61 | // Draw the rows within the visible bounds of the view 62 | std::size_t new_start = it - _cells.begin(); 63 | 64 | for (; it != _cells.end(); ++it) { 65 | auto &cell = *it; 66 | context rctx{ctx, cell.elem_ptr.get(), ctx.bounds}; 67 | make_bounds(rctx, main_axis_start, cell); 68 | if (intersects(clip_extent, rctx.bounds)) { 69 | if (!cell.elem_ptr) { 70 | cell.elem_ptr = _composer->compose(it - _cells.begin()); 71 | cell.elem_ptr->layout(rctx); 72 | cell.layout_id = _layout_id; 73 | } else if (cell.layout_id != _layout_id) { 74 | cell.elem_ptr->layout(rctx); 75 | cell.layout_id = _layout_id; 76 | } 77 | cell.elem_ptr->draw(rctx); 78 | } 79 | 80 | if (get_main_axis_start(rctx.bounds) > get_main_axis_end(clip_extent)) 81 | break; 82 | } 83 | std::size_t new_end = it - _cells.begin(); 84 | 85 | // Cleanup old rows 86 | if (new_start != _previous_window_start || new_end != _previous_window_end) { 87 | for (auto i = _previous_window_start; i != _previous_window_end; ++i) { 88 | if ((i < new_start || i >= new_end) && i < _cells.size()) { 89 | _cells[i].layout_id = -1; 90 | } 91 | } 92 | } 93 | 94 | _previous_window_start = new_start; 95 | _previous_window_end = new_end; 96 | _previous_size.x = ctx.bounds.width(); 97 | _previous_size.y = ctx.bounds.height(); 98 | } 99 | 100 | void dynamic_list_s::layout(context const &ctx) { 101 | if (_previous_size.x != ctx.bounds.width() || 102 | _previous_size.y != ctx.bounds.height()) { 103 | _previous_size.x = ctx.bounds.width(); 104 | _previous_size.y = ctx.bounds.height(); 105 | ++_layout_id; 106 | } 107 | } 108 | 109 | void dynamic_list_s::update() { 110 | _update_request = true; 111 | _cells.clear(); 112 | _main_axis_full_size = 0; 113 | } 114 | 115 | void dynamic_list_s::update(basic_context const &ctx) const { 116 | if (_composer) { 117 | if (auto size = _composer->size()) { 118 | double y = 0; 119 | _cells.reserve(_composer->size()); 120 | for (std::size_t i = 0; i != size; ++i) { 121 | auto main_axis_size = _composer->main_axis_size(i, ctx); 122 | _cells.push_back({y, main_axis_size, nullptr}); 123 | y += main_axis_size; 124 | } 125 | _main_axis_full_size = y; 126 | } 127 | } 128 | ++_layout_id; 129 | _update_request = false; 130 | } 131 | 132 | bool dynamic_list_s::click(const context &ctx, mouse_button btn) { 133 | if (!_cells.empty()) { 134 | if (btn.down) // button down 135 | { 136 | hit_info info = hit_element(ctx, btn.pos, true); 137 | if (info.element) { 138 | if (wants_focus() && _focus != info.index) 139 | new_focus(ctx, info.index); 140 | 141 | context ectx{ctx, info.element.get(), info.bounds}; 142 | if (info.element->click(ectx, btn)) { 143 | if (btn.down) 144 | _click_tracking = info.index; 145 | return true; 146 | } 147 | } 148 | } else if (_click_tracking != -1) // button up 149 | { 150 | rect bounds = bounds_of(ctx, _click_tracking); 151 | auto &e = *_composer->compose(_click_tracking); 152 | context ectx{ctx, &e, bounds}; 153 | if (e.click(ectx, btn)) { 154 | return true; 155 | } 156 | } 157 | } 158 | _click_tracking = -1; 159 | return false; 160 | } 161 | 162 | bool dynamic_list_s::text(context const &ctx, text_info info) { 163 | if (_focus != -1) { 164 | rect bounds = bounds_of(ctx, _focus); 165 | auto &focus_ = *_cells[_focus].elem_ptr; 166 | context ectx{ctx, &focus_, bounds}; 167 | return focus_.text(ectx, info); 168 | }; 169 | return false; 170 | } 171 | 172 | void dynamic_list_s::new_focus(context const &ctx, int index) { 173 | if (_focus != -1) { 174 | _composer->compose(_focus)->end_focus(); 175 | ctx.view.refresh(ctx); 176 | } 177 | 178 | // start a new focus 179 | _focus = index; 180 | if (_focus != -1 && _cells[_focus].elem_ptr != nullptr) 181 | 182 | { 183 | _cells[_focus].elem_ptr->begin_focus(); 184 | ctx.view.refresh(ctx); 185 | } 186 | } 187 | 188 | bool dynamic_list_s::key(const context &ctx, key_info k) { 189 | auto &&try_key = [&](int ix) -> bool { 190 | rect bounds = bounds_of(ctx, ix); 191 | auto &e = *_composer->compose(ix).get(); 192 | context ectx{ctx, &e, bounds}; 193 | bool b = e.key(ectx, k); 194 | return b; 195 | }; 196 | 197 | auto &&try_focus = [&](int ix) -> bool { 198 | if (_composer->compose(ix)->wants_focus()) { 199 | new_focus(ctx, ix); 200 | return true; 201 | } 202 | return false; 203 | }; 204 | 205 | if (_focus != -1) { 206 | // when tab is pressed at the end of the scroller, it doesn't automatically 207 | // scroll 208 | if (try_key(_focus)) 209 | return true; 210 | } 211 | 212 | if ((k.action == key_action::press || k.action == key_action::repeat) && 213 | k.key == key_code::tab && _cells.size()) { 214 | int next_focus = _focus; 215 | bool reverse = (k.modifiers & mod_shift) ^ reverse_index(); 216 | 217 | if ((next_focus == -1) || !reverse) { 218 | while (++next_focus != static_cast(_cells.size())) { 219 | if (try_focus(next_focus)) 220 | return true; 221 | } 222 | return false; 223 | } else { 224 | while (--next_focus >= 0) { 225 | if (_composer->compose(next_focus)->wants_focus()) 226 | if (try_focus(next_focus)) 227 | return true; 228 | } 229 | return false; 230 | } 231 | } 232 | // Johann Philippe : The following code comes from composite_base. 233 | // I commented that since it caused crashes. Still don't know if that can be 234 | // useful to adapt. 235 | 236 | // If we reached here, then there's either no focus, or the 237 | // focus did not handle the key press. 238 | 239 | /* 240 | if (reverse_index()) 241 | { 242 | for (int ix = int(_cells.size())-1; ix >= 0; --ix) 243 | { 244 | if (try_key(ix)) 245 | return true; 246 | } 247 | } 248 | else 249 | { 250 | for (std::size_t ix = 0; ix < _cells.size(); ++ix) 251 | { 252 | if (try_key(ix)) 253 | return true; 254 | } 255 | } 256 | */ 257 | return false; 258 | } 259 | 260 | bool dynamic_list_s::cursor(const context &ctx, point p, 261 | cursor_tracking status) { 262 | 263 | if (_cursor_tracking >= int(_cells.size())) // just to be sure! 264 | _cursor_tracking = -1; 265 | 266 | // Send cursor leaving to all currently hovering elements if cursor is 267 | // leaving the composite. 268 | if (status == cursor_tracking::leaving) { 269 | for (auto ix : _cursor_hovering) { 270 | if (ix < int(_cells.size())) { 271 | auto &e = *_composer->compose(ix).get(); 272 | context ectx{ctx, &e, bounds_of(ctx, ix)}; 273 | e.cursor(ectx, p, cursor_tracking::leaving); 274 | } 275 | } 276 | return false; 277 | } 278 | 279 | // Send cursor leaving to all currently hovering elements if p is 280 | // outside the elements's bounds or if the element is no longer hit. 281 | for (auto i = _cursor_hovering.begin(); i != _cursor_hovering.end();) { 282 | if (*i < int(_cells.size())) { 283 | auto &e = *_composer->compose(*i).get(); 284 | rect b = bounds_of(ctx, *i); 285 | context ectx{ctx, &e, b}; 286 | if (!b.includes(p) || !e.hit_test(ectx, p)) { 287 | e.cursor(ectx, p, cursor_tracking::leaving); 288 | i = _cursor_hovering.erase(i); 289 | continue; 290 | } 291 | } 292 | ++i; 293 | } 294 | 295 | // Send cursor entering to newly hit element or hovering to current 296 | // tracking element 297 | hit_info info = hit_element(ctx, p, true); 298 | if (info.element) { 299 | _cursor_tracking = info.index; 300 | status = cursor_tracking::hovering; 301 | if (_cursor_hovering.find(info.index) == _cursor_hovering.end()) { 302 | status = cursor_tracking::entering; 303 | _cursor_hovering.insert(_cursor_tracking); 304 | } 305 | auto &e = *_composer->compose(_cursor_tracking).get(); 306 | context ectx{ctx, &e, bounds_of(ctx, _cursor_tracking)}; 307 | return e.cursor(ectx, p, status); 308 | } 309 | 310 | return false; 311 | } 312 | 313 | void dynamic_list_s::drag(const context &ctx, mouse_button btn) { 314 | if (_click_tracking != -1) { 315 | rect bounds = bounds_of(ctx, _click_tracking); 316 | auto &e = *_composer->compose(_click_tracking).get(); 317 | context ectx{ctx, &e, bounds}; 318 | e.drag(ectx, btn); 319 | } 320 | } 321 | 322 | bool dynamic_list_s::scroll(context const &ctx, point dir, point p) { 323 | if (!_cells.empty()) { 324 | hit_info info = hit_element(ctx, p, true); 325 | if (auto ptr = info.element; 326 | ptr && elements::intersects(info.bounds, ctx.view_bounds())) { 327 | context ectx{ctx, ptr.get(), info.bounds}; 328 | return ptr->scroll(ectx, dir, p); 329 | } 330 | } 331 | return false; 332 | } 333 | 334 | bool dynamic_list_s::wants_focus() const { 335 | for (std::size_t ix = 0; ix != _cells.size(); ++ix) 336 | if (_cells[ix].elem_ptr != nullptr && _cells[ix].elem_ptr->wants_focus()) 337 | return true; 338 | return false; 339 | } 340 | 341 | bool dynamic_list_s::wants_control() const { 342 | for (std::size_t ix = 0; ix < _cells.size(); ++ix) 343 | if (_cells[ix].elem_ptr != nullptr && _cells[ix].elem_ptr->wants_control()) 344 | return true; 345 | return false; 346 | } 347 | 348 | void dynamic_list_s::begin_focus() { 349 | if (_focus == -1) 350 | _focus = _saved_focus; 351 | if (_focus == -1) { 352 | for (std::size_t ix = 0; ix != _cells.size(); ++ix) 353 | if (_cells[ix].elem_ptr != nullptr && 354 | _cells[ix].elem_ptr->wants_focus()) { 355 | _focus = ix; 356 | break; 357 | } 358 | } 359 | if (_focus != -1 && _cells[_focus].elem_ptr != nullptr) 360 | _cells[_focus].elem_ptr->begin_focus(); 361 | } 362 | 363 | void dynamic_list_s::end_focus() { 364 | if (_focus != -1 && _cells[_focus].elem_ptr != nullptr) 365 | _cells[_focus].elem_ptr->end_focus(); 366 | _saved_focus = _focus; 367 | _focus = -1; 368 | } 369 | 370 | element const *dynamic_list_s::focus() const { 371 | return (_cells.empty() || (_focus == -1)) ? 0 : _cells[_focus].elem_ptr.get(); 372 | } 373 | 374 | element *dynamic_list_s::focus() { 375 | return (_cells.empty() || (_focus == -1)) ? 0 : _cells[_focus].elem_ptr.get(); 376 | } 377 | 378 | void dynamic_list_s::focus(std::size_t index) { 379 | if (index < _cells.size()) 380 | _focus = int(index); 381 | } 382 | 383 | void dynamic_list_s::reset() { 384 | _focus = -1; 385 | _saved_focus = -1; 386 | _click_tracking = -1; 387 | _cursor_tracking = -1; 388 | _cursor_hovering.clear(); 389 | } 390 | 391 | void dynamic_list_s::resize(size_t n) { 392 | this->_composer->resize(n); 393 | this->update(); 394 | } 395 | 396 | dynamic_list_s::hit_info 397 | dynamic_list_s::hit_element(context const &ctx, point p, bool control) const { 398 | auto &&test_element = [&](int ix, hit_info &info) -> bool { 399 | if (_cells[ix].elem_ptr == nullptr) { 400 | return false; 401 | } 402 | auto &e = *_cells[ix].elem_ptr; 403 | if (!control || e.wants_control()) { 404 | rect bounds = bounds_of(ctx, ix); 405 | if (bounds.includes(p)) { 406 | context ectx{ctx, &e, bounds}; 407 | if (e.hit_test(ectx, p)) { 408 | info = hit_info{e.shared_from_this(), bounds, int(ix)}; 409 | return true; 410 | } 411 | } 412 | } 413 | return false; 414 | }; 415 | 416 | hit_info info = hit_info{{}, rect{}, -1}; 417 | if (reverse_index()) { 418 | for (int ix = int(_cells.size()) - 1; ix >= 0; --ix) 419 | if (test_element(ix, info)) 420 | break; 421 | } else { 422 | for (std::size_t ix = 0; ix < _cells.size(); ++ix) 423 | if (test_element(ix, info)) 424 | break; 425 | } 426 | return info; 427 | } 428 | 429 | //////////////////////////////////////////////////////////////////////////// 430 | // Vertical dynamic_list_s methods 431 | //////////////////////////////////////////////////////////////////////////// 432 | float dynamic_list_s::get_main_axis_start(const rect &r) { return r.top; } 433 | 434 | float dynamic_list_s::get_main_axis_end(const rect &r) { return r.bottom; } 435 | 436 | view_limits 437 | dynamic_list_s::make_limits(float main_axis_size, 438 | cell_composer::limits secondary_axis_limits) const { 439 | return {{secondary_axis_limits.min, float(main_axis_size)}, 440 | {secondary_axis_limits.max, float(main_axis_size)}}; 441 | } 442 | 443 | void dynamic_list_s::make_bounds(context &ctx, float main_axis_pos, 444 | cell_info &cell) { 445 | ctx.bounds.top = main_axis_pos + cell.pos; 446 | ctx.bounds.height(cell.main_axis_size); 447 | } 448 | 449 | rect dynamic_list_s::bounds_of(context const &ctx, int ix) const { 450 | rect r = ctx.bounds; 451 | r.top = ctx.bounds.top + _cells[ix].pos; 452 | r.height(_cells[ix].main_axis_size); 453 | return r; 454 | } 455 | 456 | } // namespace elements 457 | } // namespace cycfi 458 | -------------------------------------------------------------------------------- /src/injector/ui_elements/dynamic_list.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | /*============================================================================= 17 | Copyright (c) 2016-2020 Joel de Guzman 18 | 19 | Distributed under the MIT License [ https://opensource.org/licenses/MIT ] 20 | =============================================================================*/ 21 | 22 | #ifndef PROXINJECT_INJECTOR_DYNAMIC_LIST 23 | #define PROXINJECT_INJECTOR_DYNAMIC_LIST 24 | 25 | #include 26 | #include 27 | 28 | namespace cycfi { 29 | namespace elements { 30 | 31 | class dynamic_list_s : public element { 32 | public: 33 | using composer_ptr = std::shared_ptr; 34 | 35 | dynamic_list_s(composer_ptr composer) : _composer(composer) {} 36 | 37 | virtual view_limits limits(basic_context const &ctx) const override; 38 | void draw(context const &ctx) override; 39 | void layout(context const &ctx) override; 40 | 41 | void update(); 42 | void update(basic_context const &ctx) const; 43 | 44 | virtual bool click(const context &ctx, mouse_button btn) override; 45 | virtual bool text(context const &ctx, text_info info) override; 46 | virtual bool key(const context &ctx, key_info k) override; 47 | virtual bool cursor(context const &ctx, point p, 48 | cursor_tracking status) override; 49 | virtual bool scroll(context const &ctx, point dir, point p) override; 50 | virtual void drag(context const &ctx, mouse_button btn) override; 51 | 52 | void new_focus(context const &ctx, int index); 53 | bool wants_control() const override; 54 | bool wants_focus() const override; 55 | void begin_focus() override; 56 | void end_focus() override; 57 | element const *focus() const override; 58 | element *focus() override; 59 | void focus(std::size_t index); 60 | virtual void reset(); 61 | void resize(size_t n); 62 | 63 | struct hit_info { 64 | element_ptr element; 65 | rect bounds = rect{}; 66 | int index = -1; 67 | }; 68 | 69 | virtual rect bounds_of(context const &ctx, int ix) const; 70 | virtual bool reverse_index() const { return false; } 71 | virtual hit_info hit_element(context const &ctx, point p, bool control) const; 72 | 73 | protected: 74 | struct cell_info { 75 | double pos; 76 | double main_axis_size; 77 | element_ptr elem_ptr; 78 | int layout_id = -1; 79 | }; 80 | 81 | // virtual methods to specialize in hdynamic or vdynamic 82 | virtual view_limits 83 | make_limits(float main_axis_size, 84 | cell_composer::limits secondary_axis_limits) const; 85 | virtual float get_main_axis_start(const rect &r); 86 | virtual float get_main_axis_end(const rect &r); 87 | virtual void make_bounds(context &ctx, float main_axis_start, 88 | cell_info &info); 89 | 90 | using cells_vector = std::vector; 91 | mutable cells_vector _cells; 92 | 93 | private: 94 | composer_ptr _composer; 95 | point _previous_size; 96 | std::size_t _previous_window_start = 0; 97 | std::size_t _previous_window_end = 0; 98 | 99 | mutable double _main_axis_full_size = 0; 100 | mutable int _layout_id = 0; 101 | mutable bool _update_request = true; 102 | 103 | int _focus = -1; 104 | int _saved_focus = -1; 105 | int _click_tracking = -1; 106 | int _cursor_tracking = -1; 107 | std::set _cursor_hovering; 108 | }; 109 | 110 | } // namespace elements 111 | } // namespace cycfi 112 | 113 | #endif 114 | -------------------------------------------------------------------------------- /src/injector/ui_elements/text_box.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | /*============================================================================= 17 | Copyright (c) 2016-2020 Joel de Guzman 18 | 19 | Distributed under the MIT License [ https://opensource.org/licenses/MIT ] 20 | =============================================================================*/ 21 | 22 | #include "text_box.hpp" 23 | 24 | namespace cycfi::elements { 25 | 26 | using namespace std::chrono_literals; 27 | 28 | selectable_text_box::selectable_text_box(std::string text, font font_, 29 | float size) 30 | : static_text_box(std::move(text), font_, size), _select_start(-1), 31 | _select_end(-1), _current_x(0), _is_focus(false), _show_caret(true), 32 | _caret_started(false) {} 33 | 34 | selectable_text_box::~selectable_text_box() {} 35 | 36 | void selectable_text_box::draw(context const &ctx) { 37 | draw_selection(ctx); 38 | static_text_box::draw(ctx); 39 | draw_caret(ctx); 40 | } 41 | 42 | view_limits selectable_text_box::limits(basic_context const &ctx) const { 43 | return static_text_box::limits(ctx); 44 | } 45 | 46 | void selectable_text_box::layout(context const &ctx) { 47 | static_text_box::layout(ctx); 48 | } 49 | 50 | bool selectable_text_box::click(context const &ctx, mouse_button btn) { 51 | if (btn.state != mouse_button::left) 52 | return false; 53 | 54 | _show_caret = true; 55 | 56 | if (!btn.down) // released? return early 57 | return true; 58 | 59 | if (_text.empty()) { 60 | _select_start = _select_end = 0; 61 | scroll_into_view(ctx, false); 62 | return true; 63 | } 64 | 65 | char const *_first = _text.data(); 66 | char const *_last = _first + _text.size(); 67 | 68 | if (char const *pos = caret_position(ctx, btn.pos)) { 69 | if (btn.num_clicks != 1) { 70 | char const *end = pos; 71 | char const *start = pos; 72 | 73 | auto fixup = [&]() { 74 | if (start != _first) 75 | start = next_utf8(_last, start); 76 | _select_start = int(start - _first); 77 | _select_end = int(end - _first); 78 | }; 79 | 80 | if (btn.num_clicks == 2) { 81 | while (end < _last && !word_break(end)) 82 | end = next_utf8(_last, end); 83 | while (start > _first && !word_break(start)) 84 | start = prev_utf8(_first, start); 85 | fixup(); 86 | } else if (btn.num_clicks == 3) { 87 | while (end < _last && !is_newline(uint8_t(*end))) 88 | end++; 89 | while (start > _first && !is_newline(uint8_t(*start))) 90 | start--; 91 | fixup(); 92 | } 93 | } else { 94 | auto hit = int(pos - _first); 95 | if ((btn.modifiers == mod_shift) && (_select_start != -1)) { 96 | if (hit < _select_start) 97 | _select_start = hit; 98 | else 99 | _select_end = hit; 100 | } else { 101 | _select_end = _select_start = hit; 102 | } 103 | } 104 | _current_x = btn.pos.x - ctx.bounds.left; 105 | ctx.view.refresh(ctx); 106 | } 107 | return true; 108 | } 109 | 110 | void selectable_text_box::drag(context const &ctx, mouse_button btn) { 111 | char const *first = &_text[0]; 112 | if (char const *pos = caret_position(ctx, btn.pos)) { 113 | _select_end = int(pos - first); 114 | ctx.view.refresh(ctx); 115 | scroll_into_view(ctx, true); 116 | } 117 | } 118 | 119 | bool selectable_text_box::cursor(context const &ctx, point p, 120 | cursor_tracking /* status */) { 121 | if (ctx.bounds.includes(p)) { 122 | set_cursor(cursor_type::ibeam); 123 | return true; 124 | } 125 | return false; 126 | } 127 | 128 | namespace { 129 | void add_undo(context const &ctx, std::function &typing_state, 130 | std::function undo_f, std::function redo_f) { 131 | if (typing_state) { 132 | ctx.view.add_undo({typing_state, undo_f}); 133 | typing_state = {}; // reset 134 | } 135 | ctx.view.add_undo({undo_f, redo_f}); 136 | } 137 | } // namespace 138 | 139 | bool selectable_text_box::text(context const &ctx, text_info info_) { 140 | _show_caret = true; 141 | 142 | if (_select_start == -1) 143 | return false; 144 | 145 | if (_select_start > _select_end) 146 | std::swap(_select_end, _select_start); 147 | 148 | std::string text = codepoint_to_utf8(info_.codepoint); 149 | 150 | if (!_typing_state) 151 | _typing_state = capture_state(); 152 | 153 | // bool replace = false; 154 | // if (_select_start == _select_end) { 155 | // _text.insert(_select_start, text); 156 | //} else { 157 | // _text.replace(_select_start, _select_end - _select_start, text); 158 | // replace = true; 159 | //} 160 | 161 | //_layout.text(_text.data(), _text.data() + _text.size()); 162 | // layout(ctx); 163 | 164 | // if (replace) { 165 | // _select_end = _select_start; 166 | // scroll_into_view(ctx, true); 167 | // _select_end = _select_start += text.length(); 168 | //} else { 169 | // _select_end = _select_start += text.length(); 170 | // scroll_into_view(ctx, true); 171 | //} 172 | return true; 173 | } 174 | 175 | void selectable_text_box::set_text(string_view text_) { 176 | static_text_box::set_text(text_); 177 | _select_start = std::min(_select_start, text_.size()); 178 | _select_end = std::min(_select_end, text_.size()); 179 | } 180 | 181 | bool selectable_text_box::key(context const &ctx, key_info k) { 182 | _show_caret = true; 183 | 184 | if (_select_start == -1 || k.action == key_action::release || 185 | k.action == key_action::unknown) 186 | return false; 187 | 188 | bool move_caret = false; 189 | bool save_x = false; 190 | bool handled = false; 191 | 192 | int start = std::min(_select_end, _select_start); 193 | int end = std::max(_select_end, _select_start); 194 | std::function undo_f = capture_state(); 195 | 196 | auto up_down = [this, &ctx, k, &move_caret]() { 197 | bool up = k.key == key_code::up; 198 | glyph_metrics info; 199 | info = glyph_info(ctx, &_text[_select_end]); 200 | if (info.str) { 201 | auto y = up ? -info.line_height : +info.line_height; 202 | auto pos = point{ctx.bounds.left + _current_x, info.pos.y + y}; 203 | char const *cp = caret_position(ctx, pos); 204 | if (cp) 205 | _select_end = int(cp - &_text[0]); 206 | else 207 | _select_end = up ? 0 : int(_text.size()); 208 | move_caret = true; 209 | } 210 | }; 211 | 212 | auto next_char = [this]() { 213 | if (_select_end < static_cast(_text.size())) { 214 | char const *end = _text.data() + _text.size(); 215 | char const *p = next_utf8(end, &_text[_select_end]); 216 | _select_end = int(p - &_text[0]); 217 | } 218 | }; 219 | 220 | auto prev_char = [this]() { 221 | if (_select_end > 0) { 222 | char const *start = &_text[0]; 223 | char const *p = prev_utf8(start, &_text[_select_end]); 224 | _select_end = int(p - &_text[0]); 225 | } 226 | }; 227 | 228 | auto next_word = [this]() { 229 | if (_select_end < static_cast(_text.size())) { 230 | char const *p = &_text[_select_end]; 231 | char const *end = _text.data() + _text.size(); 232 | while (p != end && word_break(p)) 233 | p = next_utf8(end, p); 234 | while (p != end && !word_break(p)) 235 | p = next_utf8(end, p); 236 | _select_end = int(p - &_text[0]); 237 | } 238 | }; 239 | 240 | auto prev_word = [this]() { 241 | if (_select_end > 0) { 242 | char const *start = &_text[0]; 243 | char const *p = prev_utf8(start, &_text[_select_end]); 244 | while (p != start && word_break(p)) 245 | p = prev_utf8(start, p); 246 | while (p != start && !word_break(p)) 247 | p = prev_utf8(start, p); 248 | if (p != start) { 249 | char const *end = _text.data() + _text.size(); 250 | p = next_utf8(end, p); 251 | } 252 | _select_end = int(p - &_text[0]); 253 | } 254 | }; 255 | 256 | if (k.action == key_action::press || k.action == key_action::repeat) { 257 | switch (k.key) { 258 | case key_code::enter: { 259 | //_text.replace(start, end - start, "\n"); 260 | //_select_start += 1; 261 | //_select_end = _select_start; 262 | // save_x = true; 263 | // add_undo(ctx, _typing_state, undo_f, capture_state()); 264 | // handled = true; 265 | } break; 266 | 267 | case key_code::backspace: 268 | case key_code::_delete: { 269 | delete_(k.key == key_code::_delete); 270 | save_x = true; 271 | add_undo(ctx, _typing_state, undo_f, capture_state()); 272 | handled = true; 273 | } break; 274 | 275 | case key_code::left: 276 | if (_select_end != -1) { 277 | if (k.modifiers & mod_alt) 278 | prev_word(); 279 | else 280 | prev_char(); 281 | if (!(k.modifiers & mod_shift)) 282 | _select_start = _select_end = std::min(_select_start, _select_end); 283 | } 284 | move_caret = true; 285 | save_x = true; 286 | handled = true; 287 | break; 288 | 289 | case key_code::right: 290 | if (_select_end != -1) { 291 | if (k.modifiers & mod_alt) 292 | next_word(); 293 | else 294 | next_char(); 295 | if (!(k.modifiers & mod_shift)) 296 | _select_start = _select_end = std::max(_select_start, _select_end); 297 | } 298 | move_caret = true; 299 | save_x = true; 300 | handled = true; 301 | break; 302 | 303 | case key_code::up: 304 | case key_code::down: 305 | if (_select_start != -1) 306 | up_down(); 307 | handled = true; 308 | break; 309 | 310 | case key_code::a: 311 | if (k.modifiers & mod_action) { 312 | _select_start = 0; 313 | _select_end = int(_text.size()); 314 | handled = true; 315 | } 316 | break; 317 | 318 | case key_code::x: 319 | if (k.modifiers & mod_action) { 320 | cut(ctx.view, start, end); 321 | save_x = true; 322 | add_undo(ctx, _typing_state, undo_f, capture_state()); 323 | handled = true; 324 | } 325 | break; 326 | 327 | case key_code::c: 328 | if (k.modifiers & mod_action) { 329 | copy(ctx.view, start, end); 330 | handled = true; 331 | } 332 | break; 333 | 334 | case key_code::v: 335 | if (k.modifiers & mod_action) { 336 | paste(ctx.view, start, end); 337 | save_x = true; 338 | add_undo(ctx, _typing_state, undo_f, capture_state()); 339 | handled = true; 340 | } 341 | break; 342 | 343 | case key_code::z: 344 | if (k.modifiers & mod_action) { 345 | if (_typing_state) { 346 | ctx.view.add_undo({_typing_state, undo_f}); 347 | _typing_state = {}; // reset 348 | } 349 | 350 | if (k.modifiers & mod_shift) 351 | ctx.view.redo(); 352 | else 353 | ctx.view.undo(); 354 | handled = true; 355 | } 356 | break; 357 | 358 | default: 359 | break; 360 | } 361 | } 362 | 363 | if (move_caret) { 364 | clamp(_select_start, 0, int(_text.size())); 365 | clamp(_select_end, 0, int(_text.size())); 366 | if (!(k.modifiers & mod_shift)) 367 | _select_start = _select_end; 368 | } else if (handled) { 369 | _layout.text(_text.data(), _text.data() + _text.size()); 370 | layout(ctx); 371 | ctx.view.refresh(ctx); 372 | } 373 | 374 | if (handled) 375 | scroll_into_view(ctx, save_x); 376 | return handled; 377 | } 378 | 379 | bool selectable_text_box::wants_control() const { return true; } 380 | 381 | void selectable_text_box::draw_caret(context const &ctx) { 382 | if (_select_start == -1) 383 | return; 384 | 385 | if (!_is_focus) // No caret if not focused 386 | return; 387 | 388 | auto &canvas = ctx.canvas; 389 | auto const &theme = get_theme(); 390 | rect caret_bounds; 391 | bool has_caret = false; 392 | 393 | // Handle the case where text is empty 394 | if (_text.empty()) { 395 | auto size = _layout.metrics(); 396 | auto line_height = size.ascent + size.descent + size.leading; 397 | auto width = theme.text_box_caret_width; 398 | auto left = ctx.bounds.left; 399 | auto top = ctx.bounds.top; 400 | 401 | if (_show_caret) { 402 | canvas.line_width(width); 403 | canvas.stroke_style(theme.text_box_caret_color); 404 | canvas.move_to({left + (width / 2), top}); 405 | canvas.line_to({left + (width / 2), top + line_height}); 406 | canvas.stroke(); 407 | } 408 | 409 | has_caret = true; 410 | caret_bounds = rect{left, top, left + width, top + line_height}; 411 | } 412 | // Draw the caret 413 | else if (_select_start == _select_end) { 414 | auto start_info = glyph_info(ctx, _text.data() + _select_start); 415 | auto width = theme.text_box_caret_width; 416 | rect &caret = start_info.bounds; 417 | 418 | if (_show_caret) { 419 | canvas.line_width(width); 420 | canvas.stroke_style(theme.text_box_caret_color); 421 | canvas.move_to({caret.left + (width / 2), caret.top}); 422 | canvas.line_to({caret.left + (width / 2), caret.bottom}); 423 | canvas.stroke(); 424 | } 425 | 426 | has_caret = true; 427 | caret_bounds = rect{caret.left - 0.5f, caret.top, caret.left + width + 0.5f, 428 | caret.bottom}; 429 | } 430 | 431 | if (has_caret && !_caret_started) { 432 | auto tl = ctx.canvas.user_to_device(caret_bounds.top_left()); 433 | auto br = ctx.canvas.user_to_device(caret_bounds.bottom_right()); 434 | caret_bounds = {tl.x, tl.y, br.x, br.y}; 435 | 436 | _caret_started = true; 437 | ctx.view.post(500ms, [this, &_view = ctx.view, caret_bounds]() { 438 | _show_caret = !_show_caret; 439 | _view.refresh(caret_bounds); 440 | _caret_started = false; 441 | }); 442 | } 443 | } 444 | 445 | void selectable_text_box::draw_selection(context const &ctx) { 446 | if (_select_start == -1) 447 | return; 448 | 449 | auto &canvas = ctx.canvas; 450 | auto const &theme = get_theme(); 451 | 452 | if (!_text.empty()) { 453 | auto start_info = glyph_info(ctx, _text.data() + _select_start); 454 | rect &r1 = start_info.bounds; 455 | r1.right = ctx.bounds.right; 456 | 457 | auto end_info = glyph_info(ctx, _text.data() + _select_end); 458 | rect &r2 = end_info.bounds; 459 | r2.right = r2.left; 460 | r2.left = ctx.bounds.left; 461 | 462 | auto color = theme.text_box_hilite_color; 463 | if (!_is_focus) 464 | color = color.opacity(0.15); 465 | canvas.fill_style(color); 466 | if (r1.top == r2.top) { 467 | canvas.fill_rect({r1.left, r1.top, r2.right, r1.bottom}); 468 | } else { 469 | canvas.begin_path(); 470 | canvas.move_to(r1.top_left()); 471 | canvas.line_to(r1.top_right()); 472 | canvas.line_to({r1.right, r2.top}); 473 | canvas.line_to(r2.top_right()); 474 | canvas.line_to(r2.bottom_right()); 475 | canvas.line_to(r2.bottom_left()); 476 | canvas.line_to({r2.left, r1.bottom}); 477 | canvas.line_to(r1.bottom_left()); 478 | canvas.close_path(); 479 | canvas.fill(); 480 | } 481 | } 482 | } 483 | 484 | char const *selectable_text_box::caret_position(context const &ctx, point p) { 485 | auto x = ctx.bounds.left; 486 | auto y = ctx.bounds.top; 487 | auto metrics = _layout.metrics(); 488 | auto line_height = metrics.ascent + metrics.descent + metrics.leading; 489 | 490 | char const *found = nullptr; 491 | for (auto &row : _rows) { 492 | // Check if p is within this row 493 | if ((p.y >= y) && (p.y < y + line_height)) { 494 | // Check if we are at the very start of the row or beyond 495 | if (p.x <= x) { 496 | found = row.begin(); 497 | break; 498 | } 499 | 500 | // Get the actual coordinates of the glyph 501 | row.for_each([p, x, &found](char const *utf8, float left, float right) { 502 | if ((p.x >= (x + left)) && (p.x < (x + right))) { 503 | found = utf8; 504 | return false; 505 | } 506 | return true; 507 | }); 508 | // Assume it's at the end of the row if we haven't found a hit 509 | if (!found) 510 | found = row.end(); 511 | break; 512 | } 513 | y += line_height; 514 | } 515 | return found; 516 | } 517 | 518 | selectable_text_box::glyph_metrics 519 | selectable_text_box::glyph_info(context const &ctx, char const *s) { 520 | auto metrics = _layout.metrics(); 521 | auto x = ctx.bounds.left; 522 | auto y = ctx.bounds.top + metrics.ascent; 523 | auto descent = metrics.descent; 524 | auto ascent = metrics.ascent; 525 | auto leading = metrics.leading; 526 | auto line_height = ascent + descent + leading; 527 | 528 | glyph_metrics info; 529 | info.str = nullptr; 530 | info.line_height = line_height; 531 | 532 | // Check if s is at the very end 533 | if (s == _text.data() + _text.size()) { 534 | auto const &last_row = _rows.back(); 535 | auto rightmost = x + last_row.width(); 536 | auto bottom_y = y + (line_height * (_rows.size() - 1)); 537 | 538 | info.pos = {rightmost, bottom_y}; 539 | info.bounds = {rightmost, bottom_y - ascent, rightmost + 10, 540 | bottom_y + descent}; 541 | info.str = s; 542 | return info; 543 | } 544 | 545 | glyphs *prev_row = nullptr; 546 | for (auto &row : _rows) { 547 | // Check if s is within this row 548 | if (s >= row.begin() && s < row.end()) { 549 | // Get the actual coordinates of the glyph 550 | row.for_each([s, &info, x, y, ascent, descent](char const *utf8, 551 | float left, float right) { 552 | if (utf8 >= s) { 553 | info.pos = {x + left, y}; 554 | info.bounds = {x + left, y - ascent, x + right, y + descent}; 555 | info.str = utf8; 556 | return false; 557 | } 558 | return true; 559 | }); 560 | break; 561 | } 562 | // This handles the case where s is in between the start of the 563 | // current row and the end of the previous. 564 | else if (s < row.begin() && prev_row) { 565 | auto rightmost = x + prev_row->width(); 566 | auto prev_y = y - line_height; 567 | info.pos = {rightmost, prev_y}; 568 | info.bounds = {rightmost, prev_y - ascent, rightmost + 10, 569 | prev_y + descent}; 570 | info.str = s; 571 | break; 572 | } 573 | y += line_height; 574 | prev_row = &row; 575 | } 576 | 577 | return info; 578 | } 579 | 580 | void selectable_text_box::delete_(bool forward) { 581 | // auto start = std::min(_select_end, _select_start); 582 | // auto end = std::max(_select_end, _select_start); 583 | // if (start != -1) { 584 | // if (start == end) { 585 | // if (forward) { 586 | // char const *start_p = &_text[start]; 587 | // char const *end_p = &_text[0] + _text.size(); 588 | // char const *p = next_utf8(end_p, start_p); 589 | // start = int(start_p - &_text[0]); 590 | // _text.erase(start, p - start_p); 591 | // } else if (start > 0) { 592 | // char const *start_p = &_text[0]; 593 | // char const *end_p = &_text[start]; 594 | // char const *p = prev_utf8(start_p, end_p); 595 | // start = int(p - &_text[0]); 596 | // _text.erase(start, end_p - p); 597 | // } 598 | // } else { 599 | // _text.erase(start, end - start); 600 | // } 601 | // _select_end = _select_start = start; 602 | //} 603 | } 604 | 605 | void selectable_text_box::cut(view & /* v */, int start, int end) { 606 | if (start != -1 && start != end) { 607 | auto end_ = std::max(start, end); 608 | auto start_ = std::min(start, end); 609 | clipboard(_text.substr(start, end_ - start_)); 610 | delete_(false); 611 | } 612 | } 613 | 614 | void selectable_text_box::copy(view & /* v */, int start, int end) { 615 | if (start != -1 && start != end) { 616 | auto end_ = std::max(start, end); 617 | auto start_ = std::min(start, end); 618 | clipboard(_text.substr(start, end_ - start_)); 619 | } 620 | } 621 | 622 | void selectable_text_box::paste(view & /* v */, int start, int end) { 623 | // if (start != -1) { 624 | // auto end_ = std::max(start, end); 625 | // auto start_ = std::min(start, end); 626 | // std::string ins = clipboard(); 627 | // _text.replace(start, end_ - start_, ins); 628 | // start += ins.size(); 629 | // _select_end = _select_start = start; 630 | //} 631 | } 632 | 633 | struct selectable_text_box::state_saver { 634 | state_saver(selectable_text_box *this_) 635 | : text(this_->_text), select_start(this_->_select_start), 636 | select_end(this_->_select_end), save_text(this_->_text), 637 | save_select_start(this_->_select_start), 638 | save_select_end(this_->_select_end) {} 639 | 640 | void operator()() { 641 | text = save_text; 642 | select_start = save_select_start; 643 | select_end = save_select_end; 644 | } 645 | 646 | std::string &text; 647 | int &select_start; 648 | int &select_end; 649 | 650 | std::string save_text; 651 | int save_select_start; 652 | int save_select_end; 653 | }; 654 | 655 | std::function selectable_text_box::capture_state() { 656 | return state_saver(this); 657 | } 658 | 659 | void selectable_text_box::scroll_into_view(context const &ctx, bool save_x) { 660 | if (_text.empty()) { 661 | auto caret = rect{ctx.bounds.left - 1, ctx.bounds.top, ctx.bounds.left + 1, 662 | ctx.bounds.bottom}; 663 | scrollable::find(ctx).scroll_into_view(caret); 664 | ctx.view.refresh(ctx); 665 | if (save_x) 666 | _current_x = 0; 667 | return; 668 | } 669 | 670 | if (_select_end == -1) 671 | return; 672 | 673 | auto info = glyph_info(ctx, &_text[_select_end]); 674 | if (info.str) { 675 | auto caret = rect{info.bounds.left - 1, info.bounds.top, 676 | info.bounds.left + 1, info.bounds.bottom}; 677 | if (!scrollable::find(ctx).scroll_into_view(caret)) 678 | ctx.view.refresh(ctx); 679 | 680 | if (save_x) 681 | _current_x = info.pos.x - ctx.bounds.left; 682 | } 683 | } 684 | 685 | bool selectable_text_box::wants_focus() const { return true; } 686 | 687 | void selectable_text_box::begin_focus() { 688 | _is_focus = true; 689 | if (_select_start == -1) 690 | _select_start = _select_end = 0; 691 | } 692 | 693 | void selectable_text_box::end_focus() { _is_focus = false; } 694 | 695 | void selectable_text_box::select_start(int pos) { 696 | if (pos == -1 || (pos >= 0 && pos <= static_cast(_text.size()))) 697 | _select_start = pos; 698 | } 699 | 700 | void selectable_text_box::select_end(int pos) { 701 | if (pos == -1 || (pos >= 0 && pos <= static_cast(_text.size()))) 702 | _select_end = pos; 703 | } 704 | 705 | void selectable_text_box::select_all() { 706 | _select_start = 0; 707 | _select_end = int(_text.size()); 708 | } 709 | 710 | void selectable_text_box::select_none() { _select_start = _select_end = -1; } 711 | 712 | bool selectable_text_box::word_break(char const *utf8) const { 713 | auto cp = codepoint(utf8); 714 | return is_space(cp) || is_punctuation(cp); 715 | } 716 | 717 | bool selectable_text_box::line_break(char const *utf8) const { 718 | auto cp = codepoint(utf8); 719 | return is_newline(cp); 720 | } 721 | 722 | } // namespace cycfi::elements 723 | -------------------------------------------------------------------------------- /src/injector/ui_elements/text_box.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | /*============================================================================= 17 | Copyright (c) 2016-2020 Joel de Guzman 18 | 19 | Distributed under the MIT License [ https://opensource.org/licenses/MIT ] 20 | =============================================================================*/ 21 | 22 | #ifndef PROXINJECT_INJECTOR_TEXT_BOX 23 | #define PROXINJECT_INJECTOR_TEXT_BOX 24 | 25 | #include 26 | 27 | namespace cycfi::elements { 28 | 29 | struct selectable_text_box : static_text_box { 30 | public: 31 | selectable_text_box(std::string text, font font_ = get_theme().text_box_font, 32 | float size = get_theme().text_box_font_size); 33 | ~selectable_text_box(); 34 | selectable_text_box(selectable_text_box &&rhs) = default; 35 | 36 | void draw(context const &ctx) override; 37 | bool click(context const &ctx, mouse_button btn) override; 38 | void drag(context const &ctx, mouse_button btn) override; 39 | bool cursor(context const &ctx, point p, cursor_tracking status) override; 40 | bool key(context const &ctx, key_info k) override; 41 | bool wants_focus() const override; 42 | void begin_focus() override; 43 | void end_focus() override; 44 | bool wants_control() const override; 45 | void layout(context const &ctx) override; 46 | view_limits limits(basic_context const &ctx) const override; 47 | 48 | bool text(context const &ctx, text_info info) override; 49 | void set_text(string_view text) override; 50 | 51 | using element::focus; 52 | using static_text_box::get_text; 53 | 54 | int select_start() const { return _select_start; } 55 | void select_start(int pos); 56 | int select_end() const { return _select_end; } 57 | void select_end(int pos); 58 | void select_all(); 59 | void select_none(); 60 | 61 | virtual void draw_selection(context const &ctx); 62 | virtual void draw_caret(context const &ctx); 63 | virtual bool word_break(char const *utf8) const; 64 | virtual bool line_break(char const *utf8) const; 65 | 66 | protected: 67 | void scroll_into_view(context const &ctx, bool save_x); 68 | virtual void delete_(bool forward); 69 | virtual void cut(view &v, int start, int end); 70 | virtual void copy(view &v, int start, int end); 71 | virtual void paste(view &v, int start, int end); 72 | 73 | private: 74 | struct glyph_metrics { 75 | char const *str; // The start of the utf8 string 76 | point pos; // Position where glyph is drawn 77 | rect bounds; // Glyph bounds 78 | float line_height; // Line height 79 | }; 80 | 81 | char const *caret_position(context const &ctx, point p); 82 | glyph_metrics glyph_info(context const &ctx, char const *s); 83 | 84 | struct state_saver; 85 | using state_saver_f = std::function; 86 | 87 | state_saver_f capture_state(); 88 | 89 | int _select_start; 90 | int _select_end; 91 | float _current_x; 92 | state_saver_f _typing_state; 93 | bool _is_focus : 1; 94 | bool _show_caret : 1; 95 | bool _caret_started : 1; 96 | }; 97 | 98 | } // namespace cycfi::elements 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /src/injector/ui_elements/tooltip.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | /*============================================================================= 17 | Copyright (c) 2016-2020 Joel de Guzman 18 | 19 | Distributed under the MIT License [ https://opensource.org/licenses/MIT ] 20 | =============================================================================*/ 21 | 22 | #include "tooltip.hpp" 23 | #include 24 | 25 | namespace cycfi { 26 | namespace elements { 27 | rect tooltip_below_element::tip_bounds(context const &ctx) const { 28 | auto limits_ = _tip->limits(ctx); 29 | auto w = limits_.min.x; 30 | auto h = limits_.min.y; 31 | return location(rect{0, 0, w, h}, ctx.bounds); 32 | } 33 | 34 | bool tooltip_below_element::cursor(context const &ctx, point p, 35 | cursor_tracking status) { 36 | if (status != cursor_tracking::leaving) { 37 | if (_tip_status != tip_visible) { 38 | _tip_status = tip_delayed; 39 | _tip->bounds(tip_bounds(ctx)); 40 | ctx.view.post( 41 | std::chrono::duration_cast(_delay), 42 | [this, &view_ = ctx.view, bounds = ctx.bounds]() { 43 | if (_tip_status == tip_delayed) { 44 | _tip->on_cursor = [this, &view_, bounds](point p, 45 | cursor_tracking status) { 46 | _cursor_in_tip = status != cursor_tracking::leaving; 47 | if (!_cursor_in_tip && !bounds.includes(p)) 48 | close_tip(view_); 49 | }; 50 | 51 | on_hover(true); 52 | _tip_status = tip_visible; 53 | _tip->open(view_); 54 | } 55 | }); 56 | } 57 | } else { 58 | ctx.view.post([this, &view_ = ctx.view]() { 59 | if (!_cursor_in_tip) 60 | close_tip(view_); 61 | }); 62 | } 63 | 64 | return base_type::cursor(ctx, p, status); 65 | } 66 | 67 | void tooltip_below_element::close_tip(view &view_) { 68 | if (_tip_status != tip_hidden) { 69 | if (_tip_status == tip_visible) { 70 | on_hover(false); 71 | _tip->close(view_); 72 | } 73 | _tip_status = tip_hidden; 74 | _cursor_in_tip = false; 75 | } 76 | } 77 | 78 | bool tooltip_below_element::key(context const &ctx, key_info k) { 79 | auto r = base_type::key(ctx, k); 80 | if (!r && k.key == key_code::escape) 81 | close_tip(ctx.view); 82 | return r; 83 | } 84 | } // namespace elements 85 | } // namespace cycfi 86 | -------------------------------------------------------------------------------- /src/injector/ui_elements/tooltip.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | /*============================================================================= 17 | Copyright (c) 2016-2020 Joel de Guzman 18 | 19 | Distributed under the MIT License [ https://opensource.org/licenses/MIT ] 20 | =============================================================================*/ 21 | 22 | #ifndef PROXINJECT_INJECTOR_TOOLTIP 23 | #define PROXINJECT_INJECTOR_TOOLTIP 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | namespace cycfi { 31 | namespace elements { 32 | //////////////////////////////////////////////////////////////////////////// 33 | // Tooltip elements 34 | //////////////////////////////////////////////////////////////////////////// 35 | class tooltip_below_element : public proxy_base { 36 | public: 37 | using base_type = proxy_base; 38 | using on_hover_function = std::function; 39 | 40 | template 41 | tooltip_below_element(Tip &&tip, duration delay) 42 | : _tip(share(basic_popup(std::forward(tip)))), _delay(delay) {} 43 | 44 | bool cursor(context const &ctx, point p, cursor_tracking status) override; 45 | bool key(context const &ctx, key_info k) override; 46 | 47 | on_hover_function on_hover = [](bool) {}; 48 | std::function location = 49 | [](const rect &wh, const rect &ctx) { 50 | return wh.move_to(ctx.left, ctx.bottom); 51 | }; 52 | 53 | private: 54 | using popup_ptr = std::shared_ptr; 55 | enum status { tip_hidden, tip_delayed, tip_visible }; 56 | 57 | rect tip_bounds(context const &ctx) const; 58 | void close_tip(view &view_); 59 | 60 | popup_ptr _tip; 61 | status _tip_status = tip_hidden; 62 | duration _delay; 63 | bool _cursor_in_tip = false; 64 | }; 65 | 66 | template 67 | inline proxy, tooltip_below_element> 68 | tooltip_below(Subject &&subject, Tip &&tip, 69 | duration delay = milliseconds{500}) { 70 | return {std::forward(subject), std::forward(tip), delay}; 71 | } 72 | } // namespace elements 73 | } // namespace cycfi 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /src/wow64/address_dumper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PragmaTwice 2 | // 3 | // Licensed under the Apache License, 4 | // Version 2.0(the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #include 17 | 18 | #if defined(_WIN64) 19 | #error "expected x86 environment for wow64" 20 | #endif 21 | 22 | int main(int argc, char *argv[]) { 23 | const char *lib = argc > 2 ? argv[2] : "kernel32.dll", 24 | *func = argc > 1 ? argv[1] : "LoadLibraryW"; 25 | return (int)GetProcAddress(GetModuleHandleA(lib), func); 26 | } 27 | --------------------------------------------------------------------------------