├── .editorconfig ├── .github └── workflows │ └── build-and-package.yml ├── .gitignore ├── .gitmodules ├── .idea ├── .gitignore ├── KoalaBox.iml ├── cmake.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── CMakeLists.txt ├── LICENSE.txt ├── README.adoc ├── UNLICENSE.txt ├── build.ps1 ├── cmake └── KoalaBox.cmake ├── include └── koalabox │ ├── cache.hpp │ ├── config.hpp │ ├── core.hpp │ ├── crypto.hpp │ ├── dll_monitor.hpp │ ├── globals.hpp │ ├── hook.hpp │ ├── http_client.hpp │ ├── io.hpp │ ├── ipc.hpp │ ├── loader.hpp │ ├── logger.hpp │ ├── patcher.hpp │ ├── paths.hpp │ ├── pch.hpp │ ├── util.hpp │ └── win_util.hpp ├── res ├── build_config.gen.h └── version.gen.rc └── src ├── exports_generator └── exports_generator.cpp └── koalabox ├── cache.cpp ├── core.cpp ├── crypto.cpp ├── dll_monitor.cpp ├── globals.cpp ├── hook.cpp ├── http_client.cpp ├── io.cpp ├── ipc.cpp ├── loader.cpp ├── logger.cpp ├── ntapi.hpp ├── patcher.cpp ├── paths.cpp ├── util.cpp └── win_util.cpp /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = crlf 4 | indent_size = 4 5 | indent_style = space 6 | insert_final_newline = true 7 | max_line_length = 120 8 | tab_width = 4 9 | ij_continuation_indent_size = 4 10 | ij_formatter_off_tag = @formatter:off 11 | ij_formatter_on_tag = @formatter:on 12 | ij_formatter_tags_enabled = false 13 | ij_smart_tabs = false 14 | ij_visual_guides = none 15 | ij_wrap_on_typing = false 16 | 17 | [.editorconfig] 18 | ij_editorconfig_align_group_field_declarations = false 19 | ij_editorconfig_space_after_colon = false 20 | ij_editorconfig_space_after_comma = true 21 | ij_editorconfig_space_before_colon = false 22 | ij_editorconfig_space_before_comma = false 23 | ij_editorconfig_spaces_around_assignment_operators = true 24 | 25 | [{*.c,*.c++,*.cc,*.cp,*.cpp,*.cu,*.cuh,*.cxx,*.h,*.h++,*.hh,*.hp,*.hpp,*.hxx,*.i,*.icc,*.ii,*.inl,*.ino,*.ipp,*.m,*.mm,*.pch,*.tcc,*.tpp,version.gen.rc}] 26 | ij_c_add_brief_tag = false 27 | ij_c_add_getter_prefix = true 28 | ij_c_add_setter_prefix = true 29 | ij_c_align_dictionary_pair_values = false 30 | ij_c_align_group_field_declarations = false 31 | ij_c_align_init_list_in_columns = true 32 | ij_c_align_multiline_array_initializer_expression = true 33 | ij_c_align_multiline_assignment = true 34 | ij_c_align_multiline_binary_operation = true 35 | ij_c_align_multiline_chained_methods = false 36 | ij_c_align_multiline_for = true 37 | ij_c_align_multiline_ternary_operation = false 38 | ij_c_array_initializer_comma_on_next_line = false 39 | ij_c_array_initializer_new_line_after_left_brace = false 40 | ij_c_array_initializer_right_brace_on_new_line = false 41 | ij_c_array_initializer_wrap = normal 42 | ij_c_assignment_wrap = off 43 | ij_c_binary_operation_sign_on_next_line = false 44 | ij_c_binary_operation_wrap = normal 45 | ij_c_blank_lines_after_class_header = 0 46 | ij_c_blank_lines_after_imports = 1 47 | ij_c_blank_lines_around_class = 1 48 | ij_c_blank_lines_around_field = 0 49 | ij_c_blank_lines_around_field_in_interface = 0 50 | ij_c_blank_lines_around_method = 1 51 | ij_c_blank_lines_around_method_in_interface = 1 52 | ij_c_blank_lines_around_namespace = 0 53 | ij_c_blank_lines_around_properties_in_declaration = 0 54 | ij_c_blank_lines_around_properties_in_interface = 0 55 | ij_c_blank_lines_before_imports = 1 56 | ij_c_blank_lines_before_method_body = 0 57 | ij_c_block_brace_placement = end_of_line 58 | ij_c_block_brace_style = end_of_line 59 | ij_c_block_comment_at_first_column = true 60 | ij_c_catch_on_new_line = false 61 | ij_c_class_brace_style = end_of_line 62 | ij_c_class_constructor_init_list_align_multiline = true 63 | ij_c_class_constructor_init_list_comma_on_next_line = false 64 | ij_c_class_constructor_init_list_new_line_after_colon = never 65 | ij_c_class_constructor_init_list_new_line_before_colon = if_long 66 | ij_c_class_constructor_init_list_wrap = normal 67 | ij_c_copy_is_deep = false 68 | ij_c_create_interface_for_categories = true 69 | ij_c_declare_generated_methods = true 70 | ij_c_description_include_member_names = true 71 | ij_c_discharged_short_ternary_operator = false 72 | ij_c_do_not_add_breaks = false 73 | ij_c_do_while_brace_force = never 74 | ij_c_else_on_new_line = false 75 | ij_c_enum_constants_comma_on_next_line = false 76 | ij_c_enum_constants_wrap = on_every_item 77 | ij_c_for_brace_force = never 78 | ij_c_for_statement_new_line_after_left_paren = false 79 | ij_c_for_statement_right_paren_on_new_line = false 80 | ij_c_for_statement_wrap = off 81 | ij_c_function_brace_placement = end_of_line 82 | ij_c_function_call_arguments_align_multiline = false 83 | ij_c_function_call_arguments_align_multiline_pars = false 84 | ij_c_function_call_arguments_comma_on_next_line = false 85 | ij_c_function_call_arguments_new_line_after_lpar = false 86 | ij_c_function_call_arguments_new_line_before_rpar = false 87 | ij_c_function_call_arguments_wrap = normal 88 | ij_c_function_non_top_after_return_type_wrap = normal 89 | ij_c_function_parameters_align_multiline = true 90 | ij_c_function_parameters_align_multiline_pars = false 91 | ij_c_function_parameters_comma_on_next_line = false 92 | ij_c_function_parameters_new_line_after_lpar = true 93 | ij_c_function_parameters_new_line_before_rpar = true 94 | ij_c_function_parameters_wrap = normal 95 | ij_c_function_top_after_return_type_wrap = normal 96 | ij_c_generate_additional_eq_operators = true 97 | ij_c_generate_additional_rel_operators = true 98 | ij_c_generate_class_constructor = true 99 | ij_c_generate_comparison_operators_use_std_tie = false 100 | ij_c_generate_instance_variables_for_properties = ask 101 | ij_c_generate_operators_as_members = true 102 | ij_c_header_guard_style_pattern = ${PROJECT_NAME}_${FILE_NAME}_${EXT} 103 | ij_c_if_brace_force = never 104 | ij_c_in_line_short_ternary_operator = true 105 | ij_c_indent_block_comment = true 106 | ij_c_indent_c_struct_members = 4 107 | ij_c_indent_case_from_switch = true 108 | ij_c_indent_class_members = 4 109 | ij_c_indent_directive_as_code = false 110 | ij_c_indent_implementation_members = 0 111 | ij_c_indent_inside_code_block = 4 112 | ij_c_indent_interface_members = 0 113 | ij_c_indent_interface_members_except_ivars_block = false 114 | ij_c_indent_namespace_members = 4 115 | ij_c_indent_preprocessor_directive = 0 116 | ij_c_indent_visibility_keywords = 0 117 | ij_c_insert_override = true 118 | ij_c_insert_virtual_with_override = false 119 | ij_c_introduce_auto_vars = false 120 | ij_c_introduce_const_params = false 121 | ij_c_introduce_const_vars = false 122 | ij_c_introduce_generate_property = false 123 | ij_c_introduce_generate_synthesize = true 124 | ij_c_introduce_globals_to_header = true 125 | ij_c_introduce_prop_to_private_category = false 126 | ij_c_introduce_static_consts = true 127 | ij_c_introduce_use_ns_types = false 128 | ij_c_ivars_prefix = _ 129 | ij_c_keep_blank_lines_before_end = 1 130 | ij_c_keep_blank_lines_before_right_brace = 1 131 | ij_c_keep_blank_lines_in_code = 1 132 | ij_c_keep_blank_lines_in_declarations = 1 133 | ij_c_keep_case_expressions_in_one_line = false 134 | ij_c_keep_control_statement_in_one_line = true 135 | ij_c_keep_directive_at_first_column = true 136 | ij_c_keep_first_column_comment = true 137 | ij_c_keep_line_breaks = true 138 | ij_c_keep_nested_namespaces_in_one_line = false 139 | ij_c_keep_simple_blocks_in_one_line = true 140 | ij_c_keep_simple_methods_in_one_line = true 141 | ij_c_keep_structures_in_one_line = true 142 | ij_c_lambda_capture_list_align_multiline = false 143 | ij_c_lambda_capture_list_align_multiline_bracket = false 144 | ij_c_lambda_capture_list_comma_on_next_line = false 145 | ij_c_lambda_capture_list_new_line_after_lbracket = false 146 | ij_c_lambda_capture_list_new_line_before_rbracket = false 147 | ij_c_lambda_capture_list_wrap = off 148 | ij_c_line_comment_add_space = false 149 | ij_c_line_comment_at_first_column = true 150 | ij_c_method_brace_placement = end_of_line 151 | ij_c_method_call_arguments_align_by_colons = true 152 | ij_c_method_call_arguments_align_multiline = false 153 | ij_c_method_call_arguments_special_dictionary_pairs_treatment = true 154 | ij_c_method_call_arguments_wrap = off 155 | ij_c_method_call_chain_wrap = off 156 | ij_c_method_parameters_align_by_colons = true 157 | ij_c_method_parameters_align_multiline = false 158 | ij_c_method_parameters_wrap = off 159 | ij_c_namespace_brace_placement = end_of_line 160 | ij_c_parentheses_expression_new_line_after_left_paren = false 161 | ij_c_parentheses_expression_right_paren_on_new_line = false 162 | ij_c_place_assignment_sign_on_next_line = false 163 | ij_c_property_nonatomic = true 164 | ij_c_put_ivars_to_implementation = true 165 | ij_c_refactor_compatibility_aliases_and_classes = true 166 | ij_c_refactor_properties_and_ivars = true 167 | ij_c_release_style = ivar 168 | ij_c_retain_object_parameters_in_constructor = true 169 | ij_c_semicolon_after_method_signature = false 170 | ij_c_shift_operation_align_multiline = true 171 | ij_c_shift_operation_wrap = normal 172 | ij_c_show_non_virtual_functions = false 173 | ij_c_space_after_colon = true 174 | ij_c_space_after_colon_in_foreach = true 175 | ij_c_space_after_colon_in_selector = false 176 | ij_c_space_after_comma = true 177 | ij_c_space_after_cup_in_blocks = false 178 | ij_c_space_after_dictionary_literal_colon = true 179 | ij_c_space_after_for_semicolon = true 180 | ij_c_space_after_init_list_colon = true 181 | ij_c_space_after_method_parameter_type_parentheses = false 182 | ij_c_space_after_method_return_type_parentheses = false 183 | ij_c_space_after_pointer_in_declaration = true 184 | ij_c_space_after_quest = true 185 | ij_c_space_after_reference_in_declaration = true 186 | ij_c_space_after_reference_in_rvalue = false 187 | ij_c_space_after_structures_rbrace = true 188 | ij_c_space_after_superclass_colon = true 189 | ij_c_space_after_type_cast = true 190 | ij_c_space_after_visibility_sign_in_method_declaration = true 191 | ij_c_space_before_autorelease_pool_lbrace = true 192 | ij_c_space_before_catch_keyword = true 193 | ij_c_space_before_catch_left_brace = true 194 | ij_c_space_before_catch_parentheses = true 195 | ij_c_space_before_category_parentheses = true 196 | ij_c_space_before_chained_send_message = true 197 | ij_c_space_before_class_left_brace = true 198 | ij_c_space_before_colon = true 199 | ij_c_space_before_colon_in_foreach = false 200 | ij_c_space_before_comma = false 201 | ij_c_space_before_dictionary_literal_colon = false 202 | ij_c_space_before_do_left_brace = true 203 | ij_c_space_before_else_keyword = true 204 | ij_c_space_before_else_left_brace = true 205 | ij_c_space_before_for_left_brace = true 206 | ij_c_space_before_for_parentheses = true 207 | ij_c_space_before_for_semicolon = false 208 | ij_c_space_before_if_left_brace = true 209 | ij_c_space_before_if_parentheses = true 210 | ij_c_space_before_init_list = false 211 | ij_c_space_before_init_list_colon = true 212 | ij_c_space_before_method_call_parentheses = false 213 | ij_c_space_before_method_left_brace = true 214 | ij_c_space_before_method_parentheses = false 215 | ij_c_space_before_namespace_lbrace = true 216 | ij_c_space_before_pointer_in_declaration = false 217 | ij_c_space_before_property_attributes_parentheses = false 218 | ij_c_space_before_protocols_brackets = true 219 | ij_c_space_before_quest = true 220 | ij_c_space_before_reference_in_declaration = false 221 | ij_c_space_before_superclass_colon = true 222 | ij_c_space_before_switch_left_brace = true 223 | ij_c_space_before_switch_parentheses = true 224 | ij_c_space_before_template_call_lt = false 225 | ij_c_space_before_template_declaration_lt = false 226 | ij_c_space_before_try_left_brace = true 227 | ij_c_space_before_while_keyword = true 228 | ij_c_space_before_while_left_brace = true 229 | ij_c_space_before_while_parentheses = true 230 | ij_c_space_between_adjacent_brackets = false 231 | ij_c_space_between_operator_and_punctuator = false 232 | ij_c_space_within_empty_array_initializer_braces = false 233 | ij_c_spaces_around_additive_operators = true 234 | ij_c_spaces_around_assignment_operators = true 235 | ij_c_spaces_around_bitwise_operators = true 236 | ij_c_spaces_around_equality_operators = true 237 | ij_c_spaces_around_lambda_arrow = true 238 | ij_c_spaces_around_logical_operators = true 239 | ij_c_spaces_around_multiplicative_operators = true 240 | ij_c_spaces_around_pm_operators = false 241 | ij_c_spaces_around_relational_operators = true 242 | ij_c_spaces_around_shift_operators = true 243 | ij_c_spaces_around_unary_operator = false 244 | ij_c_spaces_within_array_initializer_braces = true 245 | ij_c_spaces_within_braces = true 246 | ij_c_spaces_within_brackets = false 247 | ij_c_spaces_within_cast_parentheses = false 248 | ij_c_spaces_within_catch_parentheses = false 249 | ij_c_spaces_within_category_parentheses = false 250 | ij_c_spaces_within_empty_braces = false 251 | ij_c_spaces_within_empty_function_call_parentheses = false 252 | ij_c_spaces_within_empty_function_declaration_parentheses = false 253 | ij_c_spaces_within_empty_lambda_capture_list_bracket = false 254 | ij_c_spaces_within_empty_template_call_ltgt = false 255 | ij_c_spaces_within_empty_template_declaration_ltgt = false 256 | ij_c_spaces_within_for_parentheses = false 257 | ij_c_spaces_within_function_call_parentheses = false 258 | ij_c_spaces_within_function_declaration_parentheses = false 259 | ij_c_spaces_within_if_parentheses = false 260 | ij_c_spaces_within_lambda_capture_list_bracket = false 261 | ij_c_spaces_within_method_parameter_type_parentheses = false 262 | ij_c_spaces_within_method_return_type_parentheses = false 263 | ij_c_spaces_within_parentheses = false 264 | ij_c_spaces_within_property_attributes_parentheses = false 265 | ij_c_spaces_within_protocols_brackets = false 266 | ij_c_spaces_within_send_message_brackets = false 267 | ij_c_spaces_within_switch_parentheses = false 268 | ij_c_spaces_within_template_call_ltgt = false 269 | ij_c_spaces_within_template_declaration_ltgt = false 270 | ij_c_spaces_within_template_double_gt = true 271 | ij_c_spaces_within_while_parentheses = false 272 | ij_c_special_else_if_treatment = true 273 | ij_c_superclass_list_after_colon = never 274 | ij_c_superclass_list_align_multiline = true 275 | ij_c_superclass_list_before_colon = if_long 276 | ij_c_superclass_list_comma_on_next_line = false 277 | ij_c_superclass_list_wrap = on_every_item 278 | ij_c_tag_prefix_of_block_comment = at 279 | ij_c_tag_prefix_of_line_comment = back_slash 280 | ij_c_template_call_arguments_align_multiline = false 281 | ij_c_template_call_arguments_align_multiline_pars = false 282 | ij_c_template_call_arguments_comma_on_next_line = false 283 | ij_c_template_call_arguments_new_line_after_lt = false 284 | ij_c_template_call_arguments_new_line_before_gt = false 285 | ij_c_template_call_arguments_wrap = off 286 | ij_c_template_declaration_function_body_indent = false 287 | ij_c_template_declaration_function_wrap = split_into_lines 288 | ij_c_template_declaration_struct_body_indent = false 289 | ij_c_template_declaration_struct_wrap = split_into_lines 290 | ij_c_template_parameters_align_multiline = false 291 | ij_c_template_parameters_align_multiline_pars = false 292 | ij_c_template_parameters_comma_on_next_line = false 293 | ij_c_template_parameters_new_line_after_lt = false 294 | ij_c_template_parameters_new_line_before_gt = false 295 | ij_c_template_parameters_wrap = off 296 | ij_c_ternary_operation_signs_on_next_line = true 297 | ij_c_ternary_operation_wrap = normal 298 | ij_c_type_qualifiers_placement = before 299 | ij_c_use_modern_casts = true 300 | ij_c_use_setters_in_constructor = true 301 | ij_c_while_brace_force = never 302 | ij_c_while_on_new_line = false 303 | ij_c_wrap_property_declaration = off 304 | 305 | [{*.cmake,CMakeLists.txt}] 306 | ij_continuation_indent_size = 4 307 | ij_cmake_align_multiline_parameters_in_calls = false 308 | ij_cmake_force_commands_case = 2 309 | ij_cmake_keep_blank_lines_in_code = 2 310 | ij_cmake_space_before_for_parentheses = true 311 | ij_cmake_space_before_if_parentheses = true 312 | ij_cmake_space_before_method_call_parentheses = false 313 | ij_cmake_space_before_method_parentheses = false 314 | ij_cmake_space_before_while_parentheses = true 315 | ij_cmake_spaces_within_for_parentheses = false 316 | ij_cmake_spaces_within_if_parentheses = false 317 | ij_cmake_spaces_within_method_call_parentheses = false 318 | ij_cmake_spaces_within_method_parentheses = false 319 | ij_cmake_spaces_within_while_parentheses = false 320 | 321 | [{*.har,*.json,*.jsonc}] 322 | indent_size = 2 323 | ij_json_keep_blank_lines_in_code = 1 324 | ij_json_keep_indents_on_empty_lines = false 325 | ij_json_keep_line_breaks = true 326 | ij_json_space_after_colon = true 327 | ij_json_space_after_comma = true 328 | ij_json_space_before_colon = true 329 | ij_json_space_before_comma = false 330 | ij_json_spaces_within_braces = false 331 | ij_json_spaces_within_brackets = false 332 | ij_json_wrap_long_lines = false 333 | 334 | [{*.apinotes,*.yaml,*.yml,.clang-format,.clang-tidy,_clang-format}] 335 | indent_size = 2 336 | -------------------------------------------------------------------------------- /.github/workflows/build-and-package.yml: -------------------------------------------------------------------------------- 1 | name: Build and Package 2 | on: 3 | workflow_call: 4 | inputs: 5 | modules: 6 | description: 'Stringified JSON object listing modules to build' 7 | required: true 8 | type: string 9 | 10 | zip_command: 11 | description: 'A shell command for creating a release zip' 12 | required: true 13 | type: string 14 | 15 | config: 16 | description: 'A CMake build config' 17 | required: true 18 | type: string 19 | default: Release 20 | 21 | jobs: 22 | build-project: 23 | name: Build ${{ matrix.module }} for ${{ matrix.arch }}-bit architecture 24 | runs-on: windows-2022 25 | strategy: 26 | matrix: 27 | module: ${{ fromJson(inputs.modules) }} 28 | arch: [ 32, 64 ] 29 | include: 30 | - arch: 32 31 | platform: Win32 32 | term: amd64_x86 33 | 34 | - arch: 64 35 | platform: x64 36 | term: amd64 37 | 38 | env: 39 | BUILD_DIR: ${{ github.workspace }}\build\${{ matrix.arch }}\${{ matrix.module }} 40 | 41 | steps: 42 | - name: Check out repository code 43 | uses: actions/checkout@v3 44 | with: 45 | submodules: recursive 46 | 47 | - name: Install and cache the latest CMake 48 | uses: lukka/get-cmake@latest 49 | 50 | - name: Setup Developer Command Prompt for Microsoft Visual C++. 51 | uses: ilammy/msvc-dev-cmd@v1 52 | with: 53 | arch: ${{ matrix.term }} 54 | 55 | - name: Print build directory tree 56 | run: tree /f ${{ env.BUILD_DIR }} 57 | 58 | - name: Generate build files 59 | run: cmake -G "Visual Studio 17 2022" -A "${{ matrix.platform }}" -B "${{ env.BUILD_DIR }}" -DMODULE=${{ matrix.module }} 60 | 61 | - name: Build the project 62 | run: cmake --build ${{ env.BUILD_DIR }} --config ${{ inputs.config }} 63 | 64 | - name: Upload binaries 65 | uses: actions/upload-artifact@v3 66 | with: 67 | name: ${{ matrix.module }}-${{ matrix.arch }} 68 | path: ${{ env.BUILD_DIR }}\${{ inputs.config }}\*.dll 69 | 70 | package-project: 71 | name: Package the artifacts into a release zip 72 | if: startsWith(github.ref, 'refs/tags/') 73 | runs-on: ubuntu-latest 74 | needs: build-project 75 | permissions: 76 | contents: write 77 | steps: 78 | - name: Setup version tag 79 | run: echo "VERSION_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 80 | 81 | - name: Setup zip name 82 | env: 83 | PROJECT_NAME: ${{ github.event.repository.name }} 84 | run: echo "ZIP_NAME=$PROJECT_NAME-$VERSION_TAG.zip" >> $GITHUB_ENV 85 | 86 | - name: Install required tools 87 | run: | 88 | sudo apt update 89 | sudo apt install zip tree 90 | 91 | - name: Check out repository code 92 | uses: actions/checkout@v3 93 | 94 | - name: Download all workflow run artifacts 95 | uses: actions/download-artifact@v3 96 | with: 97 | path: artifacts 98 | 99 | - name: Print artifact tree 100 | run: tree artifacts 101 | 102 | - name: Make release zip 103 | run: ${{ inputs.zip_command }} 104 | 105 | - name: Create a release draft 106 | uses: softprops/action-gh-release@v1 107 | with: 108 | body: '# 📑 Changelog' 109 | draft: true 110 | prerelease: false 111 | files: ${{ env.ZIP_NAME }} 112 | name: Release ${{ env.VERSION_TAG }} 113 | tag_name: ${{ env.VERSION_TAG }} 114 | fail_on_unmatched_files: true 115 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acidicoala/KoalaBox/842c83eecfd0b40f572853a36df4dfb24656db4e/.gitmodules -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/KoalaBox.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/cmake.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 101 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | 3 | project( 4 | KoalaBox LANGUAGES CXX 5 | DESCRIPTION "A box of koality tools" 6 | ) 7 | 8 | include(cmake/KoalaBox.cmake) 9 | 10 | # Configure variables 11 | 12 | include(GNUInstallDirs) 13 | 14 | set( 15 | KOALABOX_HEADERS 16 | include/koalabox/cache.hpp 17 | include/koalabox/config.hpp 18 | include/koalabox/core.hpp 19 | include/koalabox/crypto.hpp 20 | include/koalabox/dll_monitor.hpp 21 | include/koalabox/globals.hpp 22 | include/koalabox/logger.hpp 23 | include/koalabox/hook.hpp 24 | include/koalabox/http_client.hpp 25 | include/koalabox/io.hpp 26 | include/koalabox/ipc.hpp 27 | include/koalabox/loader.hpp 28 | include/koalabox/patcher.hpp 29 | include/koalabox/paths.hpp 30 | include/koalabox/util.hpp 31 | include/koalabox/win_util.hpp 32 | ) 33 | 34 | set( 35 | KOALABOX_SOURCES 36 | src/koalabox/cache.cpp 37 | src/koalabox/core.cpp 38 | src/koalabox/crypto.cpp 39 | src/koalabox/dll_monitor.cpp 40 | src/koalabox/globals.cpp 41 | src/koalabox/logger.cpp 42 | src/koalabox/hook.cpp 43 | src/koalabox/http_client.cpp 44 | src/koalabox/io.cpp 45 | src/koalabox/ipc.cpp 46 | src/koalabox/loader.cpp 47 | src/koalabox/ntapi.hpp 48 | src/koalabox/patcher.cpp 49 | src/koalabox/paths.cpp 50 | src/koalabox/util.cpp 51 | src/koalabox/win_util.cpp 52 | ) 53 | 54 | add_library(KoalaBox OBJECT ${KOALABOX_HEADERS} ${KOALABOX_SOURCES}) 55 | 56 | # Configure dependencies 57 | 58 | ## Cpr - https://github.com/libcpr/cpr/ 59 | fetch_library(cpr libcpr/cpr 1.10.1) 60 | 61 | ## Json - https://github.com/nlohmann/json 62 | fetch_library(nlohmann_json nlohmann/json v3.11.2) 63 | 64 | ## Polyhook2 - https://github.com/stevemk14ebr/PolyHook_2_0 65 | set(POLYHOOK_FEATURE_INLINENTD OFF) 66 | set(POLYHOOK_FEATURE_EXCEPTION OFF) 67 | fetch_library(PolyHook_2 stevemk14ebr/PolyHook_2_0 71d273463a3c4e30ec0a4031c4b477b85ea773fb) 68 | 69 | ## Spdlog - https://github.com/gabime/spdlog 70 | fetch_library(spdlog gabime/spdlog v1.11.0) 71 | 72 | ## Miniz - https://github.com/richgel999/miniz 73 | fetch_library(miniz richgel999/miniz 3.0.2) 74 | 75 | ## WinReg - https://github.com/GiovanniDicanio/WinReg 76 | fetch_library(WinReg acidicoala/WinReg b86786c6845a4a03a2e66a456cd65ec77069a7c4) 77 | 78 | # Setup KoalaBox exports 79 | target_include_directories( 80 | KoalaBox PUBLIC 81 | "$" 82 | "$" 83 | ) 84 | 85 | set(PCH_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include/koalabox/pch.hpp) 86 | target_precompile_headers(KoalaBox PUBLIC "$<$:${PCH_PATH}>") 87 | 88 | install( 89 | TARGETS KoalaBox DESTINATION ${LIB_INSTALL_DIR} 90 | INCLUDES ${KOALABOX_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR} 91 | ) 92 | 93 | # Exports Generator 94 | 95 | add_executable(exports_generator src/exports_generator/exports_generator.cpp) 96 | 97 | target_link_libraries(exports_generator PUBLIC KoalaBox) 98 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD Zero Clause License 2 | 3 | Copyright (c) 2022 by acidicoala 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = 🐨 KoalaBox 🧰 2 | 3 | A collection of C++ utilities that comes in handy when developing koality projects. 4 | 5 | To use this library as a submodule add the following to the CMake file: 6 | 7 | [source,cmake] 8 | ---- 9 | add_subdirectory(KoalaBox EXCLUDE_FROM_ALL) 10 | target_link_libraries(MyProject PRIVATE KoalaBox) 11 | ---- 12 | 13 | == 📚 Open-Source libraries 14 | 15 | This project makes use of the following libraries: 16 | 17 | * https://github.com/libcpr/cpr[C++ Requests] 18 | * https://github.com/nlohmann/json[nlohmann JSON] 19 | * https://github.com/stevemk14ebr/PolyHook_2_0[PolyHook 2] 20 | * https://github.com/gabime/spdlog[spdlog] 21 | * https://github.com/acidicoala/WinReg[WinReg] 22 | 23 | == 📄 License 24 | 25 | This software is licensed under the https://unlicense.org/[Unlicense], terms of which are available in link:UNLICENSE.txt[UNLICENSE.txt] 26 | -------------------------------------------------------------------------------- /UNLICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | # Usage: 2 | # build.ps1 32 Debug 3 | # build.ps1 32 Release 4 | # build.ps1 64 Debug 5 | # build.ps1 64 Release 6 | 7 | $target = $args[0] 8 | 9 | $arch = $args[1] 10 | if ($arch -notmatch '^(32|64)$') 11 | { 12 | throw "Invalid architecute. Expected: '32' or '64'. Got: '$arch'" 13 | } 14 | $platform = If ($arch -eq '32') 15 | { 16 | 'Win32' 17 | } 18 | Else 19 | { 20 | 'x64' 21 | } 22 | 23 | $config = $args[2] 24 | if ($config -notmatch '^(Debug|Release|RelWithDebInfo)$') 25 | { 26 | throw "Invalid architecute. Expected: 'Debug' or 'Release' or 'RelWithDebInfo'. Got: '$config'" 27 | } 28 | 29 | $Env:BUILD_DIR = "build\$arch" 30 | 31 | function Build-Project { 32 | cmake -G "Visual Studio 17 2022" -A $platform -B "$Env:BUILD_DIR" "$Env:CMAKE_OPTIONS" 33 | 34 | cmake --build "$Env:BUILD_DIR" --target $target --config $config 35 | } 36 | -------------------------------------------------------------------------------- /cmake/KoalaBox.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use") 2 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 3 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" CACHE STRING "MSVC Runtime Library") 4 | set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build DLL instead of static library") 5 | 6 | include(FetchContent) 7 | 8 | function(fetch_library LIB USER_REPO TAG) 9 | FetchContent_Declare(${LIB} GIT_REPOSITORY "https://github.com/${USER_REPO}" GIT_TAG ${TAG}) 10 | FetchContent_MakeAvailable(${LIB}) 11 | target_link_libraries(${PROJECT_NAME} PUBLIC ${LIB}) 12 | endfunction() 13 | 14 | # Sets the variable ${VAR} with val_for_32 on 32-bit build 15 | # and appends 64 to val_for_32 on 64-bit build, unless it an optional argument 16 | # is provided. 17 | function(set_32_and_64 VAR val_for_32) 18 | if (DEFINED ARGV2) 19 | set(val_for_64 ${ARGV2}) 20 | else () 21 | set(val_for_64 "${val_for_32}64") 22 | endif () 23 | 24 | if (CMAKE_SIZEOF_VOID_P EQUAL 8) 25 | set(${VAR} ${val_for_64} PARENT_SCOPE) 26 | else () 27 | set(${VAR} ${val_for_32} PARENT_SCOPE) 28 | endif () 29 | endfunction() 30 | 31 | ## Generate version resource file 32 | function(configure_version_resource FILE_DESC) 33 | set(DLL_VERSION_FILE_DESC ${FILE_DESC}) 34 | set(DLL_VERSION_PRODUCT_NAME ${CMAKE_PROJECT_NAME}) 35 | set(DLL_VERSION_INTERNAL_NAME ${CMAKE_PROJECT_NAME}) 36 | 37 | set(VERSION_RESOURCE "${CMAKE_CURRENT_BINARY_DIR}/version.rc") 38 | set(VERSION_RESOURCE ${VERSION_RESOURCE} PARENT_SCOPE) 39 | 40 | configure_file(KoalaBox/res/version.gen.rc ${VERSION_RESOURCE}) 41 | endfunction() 42 | 43 | function(configure_build_config) 44 | set(BUILD_CONFIG_HEADER "${CMAKE_CURRENT_BINARY_DIR}/build_config.h") 45 | 46 | configure_file(KoalaBox/res/build_config.gen.h ${BUILD_CONFIG_HEADER}) 47 | 48 | foreach (EXTRA_CONFIG IN LISTS ARGN) 49 | set(GENERATED_EXTRA_CONFIG ${CMAKE_CURRENT_BINARY_DIR}/${EXTRA_CONFIG}.h) 50 | 51 | file(TOUCH ${GENERATED_EXTRA_CONFIG}) 52 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/res/${EXTRA_CONFIG}.gen.h ${GENERATED_EXTRA_CONFIG}) 53 | file(APPEND ${BUILD_CONFIG_HEADER} "#include <${EXTRA_CONFIG}.h>\n") 54 | endforeach () 55 | endfunction() 56 | 57 | 58 | function(configure_output_name OUT_NAME) 59 | set_target_properties( 60 | ${CMAKE_PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_NAME "${OUT_NAME}" 61 | ) 62 | endfunction() 63 | 64 | function(configure_include_directories) 65 | target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE 66 | "${CMAKE_CURRENT_SOURCE_DIR}/src" 67 | "${CMAKE_CURRENT_BINARY_DIR}" 68 | "${ARGN}") 69 | endfunction() 70 | 71 | function(link_to_koalabox) 72 | target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE KoalaBox) 73 | endfunction() 74 | 75 | function(configure_linker_exports) 76 | cmake_parse_arguments( 77 | ARG "UNDECORATE" "FORWARDED_DLL;INPUT_SOURCES_DIR" "INPUT_DLLS;DEP_SOURCES" ${ARGN} 78 | ) 79 | 80 | string(JOIN "|" JOINED_INPUT_DLLS ${ARG_INPUT_DLLS}) 81 | 82 | set(GENERATED_LINKER_EXPORTS "${CMAKE_CURRENT_BINARY_DIR}/linker_exports.h") 83 | set(GENERATED_LINKER_EXPORTS "${GENERATED_LINKER_EXPORTS}" PARENT_SCOPE) 84 | 85 | # Make the linker_exports header available before build 86 | file(TOUCH ${GENERATED_LINKER_EXPORTS}) 87 | 88 | add_custom_command( 89 | OUTPUT ${GENERATED_LINKER_EXPORTS} 90 | COMMAND exports_generator # Executable path 91 | ${ARG_UNDECORATE} # Undecorate boolean 92 | ${ARG_FORWARDED_DLL} # Forwarded DLL path 93 | "\"${JOINED_INPUT_DLLS}\"" # Input DLLs 94 | "${GENERATED_LINKER_EXPORTS}" # Output header 95 | "${ARG_INPUT_SOURCES_DIR}" # Input sources 96 | DEPENDS exports_generator 97 | "${ARG_INPUT_DLLS}" 98 | "${ARG_DEP_SOURCES}" 99 | ) 100 | endfunction() 101 | 102 | function(install_python) 103 | # https://www.python.org/downloads/release/python-3112/ 104 | set(MY_PYTHON_VERSION 3.11.2) 105 | set_32_and_64(MY_PYTHON_INSTALLER python-${MY_PYTHON_VERSION}.exe python-${MY_PYTHON_VERSION}-amd64.exe) 106 | set_32_and_64(MY_PYTHON_INSTALLER_EXPECTED_MD5 2123016702bbb45688baedc3695852f4 4331ca54d9eacdbe6e97d6ea63526e57) 107 | 108 | set(MY_PYTHON_INSTALLER_PATH "${CMAKE_BINARY_DIR}/python-installer/${MY_PYTHON_INSTALLER}") 109 | 110 | # Download installer if necessary 111 | set(MY_PYTHON_INSTALLER_URL "https://www.python.org/ftp/python/${MY_PYTHON_VERSION}/${MY_PYTHON_INSTALLER}") 112 | 113 | MESSAGE(STATUS "Downloading python installer: ${MY_PYTHON_INSTALLER_URL}") 114 | 115 | file( 116 | DOWNLOAD ${MY_PYTHON_INSTALLER_URL} "${MY_PYTHON_INSTALLER_PATH}" 117 | EXPECTED_HASH MD5=${MY_PYTHON_INSTALLER_EXPECTED_MD5} 118 | SHOW_PROGRESS 119 | ) 120 | 121 | MESSAGE(STATUS "Installing python: ${MY_PYTHON_INSTALLER_PATH}") 122 | 123 | cmake_path(CONVERT "${CMAKE_BINARY_DIR}/python" TO_NATIVE_PATH_LIST MY_PYTHON_INSTALL_DIR NORMALIZE) 124 | 125 | # Install python 126 | execute_process( 127 | COMMAND "${MY_PYTHON_INSTALLER_PATH}" /quiet 128 | InstallAllUsers=0 129 | "TargetDir=${MY_PYTHON_INSTALL_DIR}" 130 | AssociateFiles=0 131 | PrependPath=0 132 | AppendPath=0 133 | Shortcuts=0 134 | Include_doc=0 135 | Include_debug=1 136 | Include_dev=1 137 | Include_exe=0 138 | Include_launcher=0 139 | Include_lib=1 140 | Include_pip=1 141 | Include_tcltk=1 142 | Include_test=0 143 | COMMAND_ERROR_IS_FATAL ANY 144 | COMMAND_ECHO STDOUT 145 | ) 146 | 147 | set(Python_ROOT_DIR CACHE STRING "${MY_PYTHON_INSTALLER_PATH}") 148 | 149 | find_package(Python REQUIRED COMPONENTS Development) 150 | endfunction() 151 | -------------------------------------------------------------------------------- /include/koalabox/cache.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * This namespace contains utility functions for reading from and writing to cache file on disk. 7 | * All functions are intended to be safe to call, i.e. they should not throw exceptions. 8 | */ 9 | namespace koalabox::cache { 10 | 11 | KOALABOX_API(Json) get(const String& key, const Json& value = Json()); 12 | 13 | KOALABOX_API(bool) put(const String& key, const Json& value) noexcept; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /include/koalabox/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace koalabox::config { 10 | 11 | template 12 | KOALABOX_API(Config) parse(Path config_path) { 13 | if (not exists(config_path)) { 14 | return Config(); 15 | } 16 | 17 | try { 18 | const auto config_str = koalabox::io::read_file(config_path); 19 | 20 | const auto config = Json::parse(config_str).get(); 21 | 22 | LOG_DEBUG("Parsed config:\n{}", Json(config).dump(2)) 23 | 24 | return config; 25 | } catch (const Exception& e) { 26 | util::panic("Error parsing config file: {}", e.what()); 27 | } 28 | } 29 | 30 | template 31 | KOALABOX_API(Config) parse() { 32 | return parse(koalabox::paths::get_config_path()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /include/koalabox/core.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #define SUPPRESS_UNUSED(PARAM) (void) PARAM; 12 | #define KOALABOX_API(...) [[maybe_unused]] __VA_ARGS__ 13 | 14 | #define DECLARE_STRUCT(TYPE, VAR_NAME) \ 15 | TYPE VAR_NAME = {}; \ 16 | memset(&VAR_NAME, 0, sizeof(TYPE)) 17 | 18 | #define CALL_ONCE(FUNC_BODY) \ 19 | static std::once_flag _flag; \ 20 | std::call_once(_flag, [&]() FUNC_BODY); 21 | 22 | #define NEW_THREAD(FUNC_BODY) \ 23 | std::thread( \ 24 | [=]() FUNC_BODY \ 25 | ).detach(); 26 | 27 | using Mutex = std::mutex; 28 | using MutexLockGuard = std::lock_guard; 29 | using String = std::string; 30 | using WideString = std::wstring; 31 | using Exception = std::exception; 32 | using Path = std::filesystem::path; 33 | using Json = nlohmann::json; 34 | 35 | template using Set = std::set; 36 | template using Vector = std::vector; 37 | template using Map = std::map; 38 | template using Function = std::function; 39 | 40 | constexpr auto BITNESS = 8 * sizeof(void*); 41 | 42 | // Be warned, lurker. Dark sorcery awaits ahead of you... 43 | 44 | /** 45 | * Performs case-insensitive string comparison. Usage: string1 < equals > string2 46 | * Source: https://stackoverflow.com/a/30145780 47 | * @return `true` if strings are equal, `false` otherwise 48 | */ 49 | 50 | // Useful for operators that don't modify operands 51 | template 52 | struct ConstOperatorProxy { 53 | private: 54 | const T& lhs; 55 | const U op; 56 | public: 57 | explicit ConstOperatorProxy(const T& lhs, const U& op) : lhs(lhs), op(op) {} 58 | 59 | const T& operator*() const { return lhs; } 60 | }; 61 | 62 | // Useful for operators that modify first operand 63 | template 64 | struct MutableOperatorProxy { 65 | private: 66 | T& lhs; 67 | const U op; 68 | public: 69 | explicit MutableOperatorProxy(T& lhs, const U& op) : lhs(lhs), op(op) {} 70 | 71 | T& operator*() const { return lhs; } 72 | }; 73 | 74 | /// String operators 75 | 76 | #define DEFINE_CONST_OPERATOR(TYPE, OP) \ 77 | const struct OP##_t {} OP; \ 78 | bool operator>(const ConstOperatorProxy& lhs, const TYPE& rhs); \ 79 | template \ 80 | ConstOperatorProxy operator<(const T& lhs, const OP##_t& op) { \ 81 | return ConstOperatorProxy(lhs, op); \ 82 | } 83 | 84 | DEFINE_CONST_OPERATOR(String, equals) 85 | 86 | DEFINE_CONST_OPERATOR(String, not_equals) 87 | 88 | DEFINE_CONST_OPERATOR(String, contains) 89 | 90 | /// Vector operators 91 | 92 | #define DEFINE_TEMPLATED_OPERATOR(TYPE, OP) \ 93 | const struct OP##_t {} OP; \ 94 | template \ 95 | MutableOperatorProxy operator<(T& lhs, const OP##_t& op) { \ 96 | return MutableOperatorProxy(lhs, op); \ 97 | } \ 98 | template \ 99 | void operator>(const MutableOperatorProxy, OP##_t>& lhs, const TYPE& rhs) { \ 100 | (*lhs).insert((*lhs).end(), rhs.begin(), rhs.end()); \ 101 | } 102 | 103 | DEFINE_TEMPLATED_OPERATOR(Vector, append) 104 | -------------------------------------------------------------------------------- /include/koalabox/crypto.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace koalabox::crypto { 6 | 7 | Vector decode_hex_string(const String& hex_str); 8 | 9 | String calculate_md5(const Path& file_path); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /include/koalabox/dll_monitor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * DLL Monitor starts a DLL load listener and calls the provided callback function 7 | * to notify of the load events that match the provided library name(s). 8 | */ 9 | namespace koalabox::dll_monitor { 10 | 11 | KOALABOX_API(void) init_listener( 12 | const Vector& target_library_names, 13 | const Function& callback 14 | ); 15 | 16 | KOALABOX_API(void) init_listener( 17 | const String& target_library_name, 18 | const Function& callback 19 | ); 20 | 21 | KOALABOX_API(void) shutdown_listener(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /include/koalabox/globals.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace koalabox::globals { 6 | 7 | /** 8 | * @return A handle representing the project DLL. Usually obtained from DllMain. 9 | */ 10 | KOALABOX_API(HMODULE) get_self_handle(); 11 | 12 | KOALABOX_API(String) get_project_name(bool validate = true); 13 | 14 | KOALABOX_API(void) init_globals(HMODULE handle, String name); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /include/koalabox/hook.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace koalabox::hook { 6 | 7 | KOALABOX_API(void) detour_or_throw( 8 | uintptr_t address, 9 | const String& function_name, 10 | uintptr_t callback_function 11 | ); 12 | 13 | KOALABOX_API(void) detour_or_throw( 14 | const HMODULE& module_handle, 15 | const String& function_name, 16 | uintptr_t callback_function 17 | ); 18 | 19 | KOALABOX_API(void) detour_or_warn( 20 | uintptr_t address, 21 | const String& function_name, 22 | uintptr_t callback_function 23 | ); 24 | 25 | KOALABOX_API(void) detour_or_warn( 26 | const HMODULE& module_handle, 27 | const String& function_name, 28 | uintptr_t callback_function 29 | ); 30 | 31 | KOALABOX_API(void) detour( 32 | uintptr_t address, 33 | const String& function_name, 34 | uintptr_t callback_function 35 | ); 36 | 37 | KOALABOX_API(void) detour( 38 | const HMODULE& module_handle, 39 | const String& function_name, 40 | uintptr_t callback_function 41 | ); 42 | 43 | KOALABOX_API(void) eat_hook_or_throw( 44 | const HMODULE& module_handle, 45 | const String& function_name, 46 | uintptr_t callback_function 47 | ); 48 | 49 | KOALABOX_API(void) eat_hook_or_warn( 50 | const HMODULE& module_handle, 51 | const String& function_name, 52 | uintptr_t callback_function 53 | ); 54 | 55 | KOALABOX_API(void) swap_virtual_func_or_throw( 56 | const void* instance, 57 | const String& function_name, 58 | int ordinal, 59 | uintptr_t callback_function 60 | ); 61 | 62 | KOALABOX_API(void) swap_virtual_func( 63 | const void* instance, 64 | const String& function_name, 65 | int ordinal, 66 | uintptr_t callback_function 67 | ); 68 | 69 | KOALABOX_API(uintptr_t) get_original_function( 70 | const HMODULE& library, 71 | const String& function_name 72 | ); 73 | 74 | KOALABOX_API(uintptr_t) get_original_hooked_function(const String& function_name); 75 | 76 | template 77 | KOALABOX_API(F) get_original_function(const HMODULE& library, const String& function_name, F) { 78 | return reinterpret_cast(get_original_function(library, function_name)); 79 | } 80 | 81 | template 82 | KOALABOX_API(F) 83 | get_original_hooked_function(const String& function_name, F) { 84 | return reinterpret_cast(get_original_hooked_function(function_name)); 85 | } 86 | 87 | KOALABOX_API(void) init(bool print_info = false); 88 | 89 | KOALABOX_API(bool) is_hook_mode(const HMODULE& self_module, const String& orig_library_name); 90 | 91 | } 92 | -------------------------------------------------------------------------------- /include/koalabox/http_client.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace koalabox::http_client { 6 | 7 | KOALABOX_API(Json) get_json(const String& url); 8 | 9 | KOALABOX_API(Json) post_json(const String& url, Json json); 10 | 11 | KOALABOX_API(String) head_etag(const String& url); 12 | 13 | KOALABOX_API(String) download_file(const String& url, const Path& destination); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /include/koalabox/io.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace koalabox::io { 6 | 7 | String read_file(const Path& file_path); 8 | 9 | /** 10 | * Write a string to file at the given path. 11 | * @return `true` if operation was successful, `false` otherwise 12 | */ 13 | bool write_file(const Path& file_path, const String& contents) noexcept; 14 | 15 | bool unzip_file( 16 | const Path& source_zip, 17 | const String& target_file, 18 | const Path& destination_dir 19 | ) noexcept; 20 | 21 | bool is_local_port_in_use(int port); 22 | } 23 | -------------------------------------------------------------------------------- /include/koalabox/ipc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace koalabox::ipc { 6 | 7 | struct Request { 8 | String name; 9 | Json::object_t args; 10 | 11 | NLOHMANN_DEFINE_TYPE_INTRUSIVE(Request, name, args); 12 | }; 13 | 14 | struct Response { 15 | bool success = false; 16 | Json::object_t data; 17 | 18 | NLOHMANN_DEFINE_TYPE_INTRUSIVE(Response, success, data); 19 | }; 20 | 21 | /// Note: This function is intended to be called from a new thread, in a try-catch block. 22 | KOALABOX_API(void) init_pipe_server( 23 | const String& pipe_id, 24 | const Function& callback 25 | ); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /include/koalabox/loader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace koalabox::loader { 6 | 7 | KOALABOX_API(Path) get_module_dir(const HMODULE& handle); 8 | 9 | KOALABOX_API(Map) get_export_map(const HMODULE& library, bool undecorate = false); 10 | 11 | KOALABOX_API(String) get_decorated_function(const HMODULE& library, const String& function_name); 12 | 13 | KOALABOX_API(HMODULE) load_original_library(const Path& self_path, const String& orig_library_name); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /include/koalabox/logger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | 8 | namespace koalabox::logger { 9 | /** 10 | * A global single instance of the logger. Not meant to be used directly, 11 | * as it is considered implementation detail. Instead, callers should use 12 | * the macros defined below. 13 | */ 14 | extern std::shared_ptr instance; 15 | 16 | KOALABOX_API(void) init_file_logger(const Path& path); 17 | 18 | KOALABOX_API(void) init_file_logger(); 19 | 20 | KOALABOX_API(String) get_filename(const char* full_path); 21 | } 22 | 23 | #define LOG_MESSAGE(LEVEL, fmt, ...) koalabox::logger::instance->LEVEL( \ 24 | " {:>3}:{:24} ┃ " fmt, __LINE__, koalabox::logger::get_filename(__FILE__) , __VA_ARGS__ \ 25 | ); 26 | 27 | // Define trace in a special way to avoid excessive logging in release builds 28 | #ifdef _DEBUG 29 | #define LOG_TRACE(fmt, ...) LOG_MESSAGE(trace, fmt, __VA_ARGS__) 30 | #else 31 | #define LOG_TRACE(...) 32 | #endif 33 | 34 | #define LOG_DEBUG(fmt, ...) LOG_MESSAGE(debug, fmt, __VA_ARGS__) 35 | #define LOG_WARN(fmt, ...) LOG_MESSAGE(warn, fmt, __VA_ARGS__) 36 | #define LOG_INFO(fmt, ...) LOG_MESSAGE(info, fmt, __VA_ARGS__) 37 | #define LOG_ERROR(fmt, ...) LOG_MESSAGE(error, fmt, __VA_ARGS__) 38 | #define LOG_CRITICAL(fmt, ...) LOG_MESSAGE(critical, fmt, __VA_ARGS__) 39 | -------------------------------------------------------------------------------- /include/koalabox/patcher.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace koalabox::patcher { 6 | 7 | KOALABOX_API(uintptr_t) find_pattern_address( 8 | uintptr_t base_address, 9 | size_t scan_size, 10 | const String& name, 11 | const String& pattern 12 | ); 13 | 14 | KOALABOX_API(uintptr_t) find_pattern_address( 15 | const MODULEINFO& process_info, 16 | const String& name, 17 | const String& pattern 18 | ); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /include/koalabox/paths.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace koalabox::paths { 6 | 7 | /** 8 | * @return An std::path instance representing the directory containing this DLL 9 | */ 10 | KOALABOX_API(Path) get_self_path(); 11 | 12 | KOALABOX_API(Path) get_config_path(); 13 | 14 | KOALABOX_API(Path) get_cache_path(); 15 | 16 | KOALABOX_API(Path) get_log_path(); 17 | 18 | KOALABOX_API(Path) get_ca_key_path(); 19 | 20 | KOALABOX_API(Path) get_ca_cert_path(); 21 | 22 | KOALABOX_API(Path) get_cache_dir(); 23 | 24 | KOALABOX_API(Path) get_user_dir(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /include/koalabox/pch.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Windows headers 4 | #define WIN32_LEAN_AND_MEAN 5 | #define _UNICODE 6 | #define UNICODE 7 | #define NOMINMAX 8 | #include 9 | #include 10 | 11 | // Process Status API must be included after windows 12 | #include 13 | -------------------------------------------------------------------------------- /include/koalabox/util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #define KException koalabox::util::exception 8 | #define STR koalabox::util::to_string 9 | #define WSTR koalabox::util::to_wstring 10 | 11 | namespace koalabox::util { 12 | 13 | KOALABOX_API(void) error_box(const String& title, const String& message); 14 | 15 | [[noreturn]] KOALABOX_API(void) panic(String message); 16 | 17 | template 18 | [[noreturn]] KOALABOX_API(void) panic(fmt::format_string fmt, Args&& ... args) { 19 | const auto message = fmt::format(fmt, std::forward(args)...); 20 | 21 | panic(message); 22 | } 23 | 24 | KOALABOX_API(String) to_string(const WideString& wstr); 25 | 26 | KOALABOX_API(WideString) to_wstring(const String& str); 27 | 28 | template 29 | KOALABOX_API(Exception) exception(fmt::format_string fmt, Args&& ...args) { 30 | return std::runtime_error(fmt::format(fmt, std::forward(args)...)); 31 | } 32 | 33 | KOALABOX_API(bool) is_valid_pointer(const void* pointer); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /include/koalabox/win_util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define GET_LAST_ERROR koalabox::win_util::get_last_error 6 | 7 | namespace koalabox::win_util { 8 | 9 | KOALABOX_API(PROCESS_INFORMATION) create_process( 10 | const String& app_name, 11 | const String& args, 12 | const Path& working_dir, 13 | bool show_window 14 | ); 15 | 16 | KOALABOX_API(String) format_message(DWORD message_id); 17 | 18 | KOALABOX_API(String) get_last_error(); 19 | 20 | KOALABOX_API(String) get_module_file_name_or_throw(const HMODULE& module_handle); 21 | 22 | KOALABOX_API(String) get_module_file_name(const HMODULE& module_handle); 23 | 24 | KOALABOX_API(HMODULE) get_module_handle_or_throw(LPCSTR module_name); 25 | 26 | KOALABOX_API(HMODULE) get_module_handle(LPCSTR module_name); 27 | 28 | KOALABOX_API(MODULEINFO) get_module_info_or_throw(const HMODULE& module_handle); 29 | 30 | KOALABOX_API(MODULEINFO) get_module_info(const HMODULE& module_handle); 31 | 32 | KOALABOX_API(String) get_module_manifest(const HMODULE& module_handle); 33 | 34 | KOALABOX_API(String) get_module_version_or_throw(const HMODULE& module_handle); 35 | 36 | KOALABOX_API(String) get_pe_section_data_or_throw( 37 | const HMODULE& module_handle, 38 | const String& section_name 39 | ); 40 | 41 | KOALABOX_API(String) get_pe_section_data( 42 | const HMODULE& module_handle, 43 | const String& section_name 44 | ); 45 | 46 | KOALABOX_API(FARPROC) get_proc_address_or_throw( 47 | const HMODULE& module_handle, 48 | LPCSTR procedure_name 49 | ); 50 | 51 | KOALABOX_API(FARPROC) get_proc_address(const HMODULE& module_handle, LPCSTR procedure_name); 52 | 53 | KOALABOX_API(Path) get_system_directory_or_throw(); 54 | 55 | KOALABOX_API(Path) get_system_directory(); 56 | 57 | KOALABOX_API(void) free_library_or_throw(const HMODULE& module_handle); 58 | 59 | KOALABOX_API(bool) free_library(const HMODULE& module_handle, bool panic_on_fail = false); 60 | 61 | KOALABOX_API(HMODULE) load_library_or_throw(const Path& module_path); 62 | 63 | KOALABOX_API(HMODULE) load_library(const Path& module_path); 64 | 65 | KOALABOX_API(void) register_application_restart(); 66 | 67 | KOALABOX_API(std::optional) virtual_query(const void* pointer); 68 | 69 | KOALABOX_API(SIZE_T) write_process_memory_or_throw( 70 | const HANDLE& process, LPVOID address, LPCVOID buffer, SIZE_T size 71 | ); 72 | 73 | SIZE_T write_process_memory(const HANDLE& process, LPVOID address, LPCVOID buffer, SIZE_T size); 74 | 75 | } 76 | -------------------------------------------------------------------------------- /res/build_config.gen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define PROJECT_VERSION "${PROJECT_VERSION}" 4 | #define PROJECT_NAME "${CMAKE_PROJECT_NAME}" 5 | -------------------------------------------------------------------------------- /res/version.gen.rc: -------------------------------------------------------------------------------- 1 | #pragma code_page(65001) // UTF-8 2 | #include 3 | 4 | #define PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR},${CMAKE_PROJECT_VERSION_MINOR},${CMAKE_PROJECT_VERSION_PATCH},0 5 | #define PROJECT_VERSION_STR "${PROJECT_VERSION}" 6 | 7 | #ifdef _DEBUG 8 | #define PROJECT_FILEFLAGS VS_FF_DEBUG 9 | #else 10 | #define PROJECT_FILEFLAGS 0 11 | #endif 12 | 13 | VS_VERSION_INFO VERSIONINFO 14 | FILEVERSION PROJECT_VERSION 15 | PRODUCTVERSION PROJECT_VERSION 16 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 17 | FILEFLAGS PROJECT_FILEFLAGS 18 | FILEOS VOS_NT_WINDOWS32 19 | FILETYPE VFT_DLL 20 | FILESUBTYPE VFT2_UNKNOWN 21 | BEGIN 22 | BLOCK "StringFileInfo" 23 | BEGIN 24 | BLOCK "040904E4" 25 | BEGIN 26 | VALUE "CompanyName", "ʕ •ᴥ•ʔ acidicoala" 27 | VALUE "FileDescription", "${DLL_VERSION_FILE_DESC}" 28 | VALUE "FileVersion", PROJECT_VERSION_STR 29 | VALUE "InternalName", "${DLL_VERSION_INTERNAL_NAME}" 30 | VALUE "LegalCopyright", "Fuck the copyright 🖕" 31 | VALUE "OriginalFilename", "${CMAKE_PROJECT_NAME}.dll" 32 | VALUE "ProductName", "${DLL_VERSION_PRODUCT_NAME}" 33 | VALUE "ProductVersion", PROJECT_VERSION_STR 34 | END 35 | END 36 | 37 | BLOCK "VarFileInfo" 38 | BEGIN 39 | VALUE "Translation", 0x409, 1252 40 | END 41 | END 42 | -------------------------------------------------------------------------------- /src/exports_generator/exports_generator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | using namespace koalabox; 13 | 14 | bool parseBoolean(const String& bool_str); 15 | 16 | Set get_implemented_functions(const Path& path); 17 | 18 | Vector split_string(const String& s, const String& delimiter); 19 | 20 | /** 21 | * Args
22 | * 0: program name
23 | * 1: undecorate?
24 | * 2: forwarded dll name
25 | * 3: dll input paths delimited by pipe (|)
26 | * 4: header output path
27 | * 5: sources input path
(optional) 28 | */ 29 | int wmain(const int argc, const wchar_t* argv[]) { 30 | logger::instance = spdlog::stdout_logger_st("stdout"); 31 | 32 | try { 33 | auto formatter = std::make_unique(); 34 | formatter->set_pattern("%H:%M:%S.%e │ %l │ %v"); 35 | logger::instance->set_formatter(std::move(formatter)); 36 | logger::instance->flush_on(spdlog::level::trace); 37 | logger::instance->set_level(spdlog::level::trace); 38 | 39 | for (int i = 0; i < argc; i++) { 40 | LOG_INFO("Arg #{} = '{}'", i, util::to_string(argv[i])) 41 | } 42 | 43 | if (argc < 5 || argc > 6) { 44 | LOG_ERROR("Invalid number of arguments. Expected 5 or 6. Got: {}", argc) 45 | 46 | exit(1); 47 | } 48 | 49 | const auto undecorate = parseBoolean(util::to_string(argv[1])); 50 | const auto forwarded_dll_name = util::to_string(argv[2]); 51 | const auto path_strings = split_string(util::to_string(argv[3]), "|"); 52 | const auto header_output_path = Path(argv[4]); 53 | const auto sources_input_path = Path(argc == 6 ? argv[5] : L""); // Optional for Koaloader 54 | 55 | const auto implemented_functions = sources_input_path.empty() 56 | ? Set() 57 | : get_implemented_functions(sources_input_path); 58 | 59 | if (path_strings.empty()) { 60 | LOG_ERROR("Failed to parse any dll input paths") 61 | exit(2); 62 | } 63 | 64 | Map exported_functions; 65 | for (const auto& path_string: path_strings) { 66 | const auto path = Path(util::to_wstring(path_string)); 67 | 68 | if (not exists(path)) { 69 | LOG_ERROR("Non-existent DLL path: {}", path.string()) 70 | exit(3); 71 | } 72 | 73 | auto* const library = win_util::load_library_or_throw(path); 74 | const auto lib_exports = loader::get_export_map(library, undecorate); 75 | 76 | exported_functions.insert(lib_exports.begin(), lib_exports.end()); 77 | } 78 | 79 | // Create directories for export header, if necessary 80 | create_directories(header_output_path.parent_path()); 81 | 82 | // Open the export header file for writing 83 | std::ofstream export_file(header_output_path, std::ofstream::out | std::ofstream::trunc); 84 | if (not export_file.is_open()) { 85 | LOG_ERROR("Filed to open header file for writing") 86 | exit(4); 87 | } 88 | 89 | // Add header guard 90 | export_file << "#pragma once" << std::endl << std::endl; 91 | 92 | // Iterate over exported functions to exclude implemented ones 93 | for (const auto& [function_name, decorated_function_name]: exported_functions) { 94 | auto comment = implemented_functions.contains(function_name); 95 | 96 | const String line = fmt::format( 97 | R"({}#pragma comment(linker, "/export:{}={}.{}"))", 98 | comment ? "// " : "", 99 | decorated_function_name, 100 | forwarded_dll_name, 101 | decorated_function_name 102 | ); 103 | 104 | export_file << line << std::endl; 105 | } 106 | } catch (const Exception& ex) { 107 | LOG_ERROR("Error: {}", ex.what()) 108 | exit(-1); 109 | } 110 | } 111 | 112 | /** 113 | * Returns a list of functions parsed from the sources 114 | * in a given directory. Edge cases: Comments 115 | */ 116 | Set get_implemented_functions(const Path& path) { 117 | Set implemented_functions; 118 | 119 | for (const auto& p: std::filesystem::recursive_directory_iterator(path)) { 120 | const auto& file_path = p.path(); 121 | 122 | std::ifstream ifs(file_path); 123 | std::string file_content(std::istreambuf_iterator{ifs}, {}); 124 | 125 | // Matches function name in the 1st group 126 | static const std::regex func_name_pattern(R"(\s*DLL_EXPORT\([\w|\s]+\**\)\s*(\w+)\s*\()"); 127 | std::smatch match; 128 | while (regex_search(file_content, match, func_name_pattern)) { 129 | const auto func_name = match.str(1); 130 | 131 | if (not implemented_functions.contains(func_name)) { 132 | implemented_functions.insert(func_name); 133 | LOG_INFO("Implemented function: \"{}\"", func_name) 134 | } 135 | 136 | file_content = match.suffix(); 137 | } 138 | } 139 | 140 | return implemented_functions; 141 | } 142 | 143 | bool parseBoolean(const String& bool_str) { 144 | if (bool_str < equals > "true") { 145 | return true; 146 | } 147 | 148 | if (bool_str < equals > "false") { 149 | return false; 150 | } 151 | 152 | LOG_ERROR("Invalid boolean value: {}", bool_str) 153 | exit(10); 154 | } 155 | 156 | Vector split_string(const String& s, const String& delimiter) { 157 | size_t pos_start = 0; 158 | size_t pos_end; 159 | const size_t delimiter_len = delimiter.length(); 160 | String token; 161 | Vector res; 162 | 163 | while ((pos_end = s.find(delimiter, pos_start)) != String::npos) { 164 | token = s.substr(pos_start, pos_end - pos_start); 165 | pos_start = pos_end + delimiter_len; 166 | res.push_back(token); 167 | } 168 | 169 | res.push_back(s.substr(pos_start)); 170 | return res; 171 | } 172 | -------------------------------------------------------------------------------- /src/koalabox/cache.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace koalabox::cache { 7 | 8 | namespace { 9 | Json read_cache() { 10 | return Json::parse( 11 | io::read_file( 12 | koalabox::paths::get_cache_path() 13 | ) 14 | ); 15 | } 16 | } 17 | 18 | KOALABOX_API(Json) get(const String& key, const Json& fallback) { 19 | const auto cache = read_cache(); 20 | 21 | LOG_DEBUG("Cache key: \"{}\". Value: \n{}", key, cache.dump(2)) 22 | 23 | if (cache.contains(key)) { 24 | return cache.at(key); 25 | } 26 | 27 | return fallback; 28 | } 29 | 30 | KOALABOX_API(bool) put(const String& key, const Json& value) noexcept { 31 | try { 32 | static Mutex mutex; 33 | const MutexLockGuard lock(mutex); 34 | 35 | Json new_cache; 36 | try { 37 | new_cache = read_cache(); 38 | } catch (const Exception& e) { 39 | LOG_WARN("Failed to read cache from disk: {}", e.what()) 40 | } 41 | 42 | new_cache[key] = value; 43 | 44 | io::write_file(koalabox::paths::get_cache_path(), new_cache.dump(2)); 45 | 46 | return true; 47 | } catch (const Exception& e) { 48 | LOG_ERROR("Failed to write cache to disk: {}", e.what()) 49 | 50 | return false; 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/koalabox/core.cpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | bool operator>(const ConstOperatorProxy& lhs, const String& rhs) { 6 | return _stricmp((*lhs).c_str(), rhs.c_str()) == 0; 7 | } 8 | 9 | bool operator>(const ConstOperatorProxy& lhs, const String& rhs) { 10 | return not(*lhs < equals > rhs); 11 | } 12 | 13 | bool operator>(const ConstOperatorProxy& lhs, const String& rhs) { 14 | return (*lhs).find(rhs) != String::npos; 15 | } 16 | -------------------------------------------------------------------------------- /src/koalabox/crypto.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace koalabox::crypto { 9 | 10 | Vector decode_hex_string(const String& hex_str) { 11 | if (hex_str.length() < 2) { 12 | return {}; 13 | } 14 | 15 | Vector buffer(hex_str.size() / 2); 16 | 17 | std::stringstream ss; 18 | ss << std::hex << hex_str; 19 | 20 | for (size_t i = 0; i < hex_str.length(); i++) { 21 | ss >> buffer[i]; 22 | } 23 | 24 | return buffer; 25 | } 26 | 27 | // Source: https://learn.microsoft.com/en-us/windows/win32/seccrypto/example-c-program--creating-an-md-5-hash-from-file-content 28 | String calculate_md5(const Path& file_path) { 29 | String result_buffer(32, '\0'); 30 | const auto buffer_size = 1024 * 1024; // 1 Mb 31 | const auto md5_len = 16; 32 | 33 | BOOL result = FALSE; 34 | HCRYPTPROV hProv = 0; 35 | HCRYPTHASH hHash = 0; 36 | HANDLE hFile = NULL; 37 | 38 | hFile = CreateFile( 39 | file_path.wstring().c_str(), 40 | GENERIC_READ, 41 | FILE_SHARE_READ, 42 | NULL, 43 | OPEN_EXISTING, 44 | FILE_FLAG_SEQUENTIAL_SCAN, 45 | NULL 46 | ); 47 | 48 | if (INVALID_HANDLE_VALUE == hFile) { 49 | LOG_ERROR( 50 | "Error opening file {}. Error: {}", 51 | file_path.string(), win_util::get_last_error() 52 | ) 53 | 54 | return ""; 55 | } 56 | 57 | // Get handle to the crypto provider 58 | if (!CryptAcquireContext( 59 | &hProv, 60 | NULL, 61 | NULL, 62 | PROV_RSA_FULL, 63 | CRYPT_VERIFYCONTEXT 64 | )) { 65 | LOG_ERROR("CryptAcquireContext error. Error: {}", win_util::get_last_error()) 66 | 67 | CloseHandle(hFile); 68 | return ""; 69 | } 70 | 71 | if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { 72 | LOG_ERROR("CryptAcquireContext error. Error: {}", win_util::get_last_error()) 73 | 74 | CloseHandle(hFile); 75 | CryptReleaseContext(hProv, 0); 76 | return ""; 77 | } 78 | 79 | BYTE rgb_file[buffer_size]; 80 | DWORD bytes_read = 0; 81 | while ((result = ReadFile( 82 | hFile, 83 | rgb_file, 84 | buffer_size, 85 | &bytes_read, 86 | NULL 87 | ))) { 88 | if (not bytes_read) { 89 | break; 90 | } 91 | 92 | if (!CryptHashData(hHash, rgb_file, bytes_read, 0)) { 93 | LOG_ERROR("CryptHashData error. Error: {}", win_util::get_last_error()) 94 | 95 | CryptReleaseContext(hProv, 0); 96 | CryptDestroyHash(hHash); 97 | CloseHandle(hFile); 98 | return ""; 99 | } 100 | } 101 | 102 | if (!result) { 103 | LOG_ERROR("ReadFile error. Error: {}", win_util::get_last_error()) 104 | 105 | CryptReleaseContext(hProv, 0); 106 | CryptDestroyHash(hHash); 107 | CloseHandle(hFile); 108 | return ""; 109 | } 110 | 111 | BYTE rgb_hash[md5_len]; 112 | CHAR rgb_digits[] = "0123456789abcdef"; 113 | DWORD hash_length = md5_len; 114 | if (CryptGetHashParam(hHash, HP_HASHVAL, rgb_hash, &hash_length, 0)) { 115 | for (DWORD i = 0; i < hash_length; i++) { 116 | result_buffer[i * 2] = rgb_digits[rgb_hash[i] >> 4]; 117 | result_buffer[i * 2 + 1] = rgb_digits[rgb_hash[i] & 0xf]; 118 | } 119 | } else { 120 | LOG_ERROR("ReadFile CryptGetHashParam. Error: {}", win_util::get_last_error()) 121 | } 122 | 123 | CryptDestroyHash(hHash); 124 | CryptReleaseContext(hProv, 0); 125 | CloseHandle(hFile); 126 | 127 | return result_buffer; 128 | } 129 | 130 | } -------------------------------------------------------------------------------- /src/koalabox/dll_monitor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace koalabox::dll_monitor { 8 | 9 | PVOID cookie = nullptr; 10 | 11 | KOALABOX_API(void) init_listener( 12 | const String& target_library_name, 13 | const Function& callback 14 | ) { 15 | init_listener( 16 | Vector{target_library_name}, [=](const HMODULE& module_handle, const String&) { 17 | callback(module_handle); 18 | } 19 | ); 20 | } 21 | 22 | KOALABOX_API(void) init_listener( 23 | const Vector& target_library_names, 24 | const Function& callback 25 | ) { 26 | if (cookie) { 27 | LOG_ERROR("Already initialized") 28 | return; 29 | } 30 | 31 | // First start listening for future DLLs 32 | 33 | LOG_DEBUG("Initializing DLL monitor") 34 | 35 | struct CallbackData { 36 | Vector target_library_names; 37 | Function callback; 38 | }; 39 | 40 | // Pre-process the notification 41 | const auto notification_listener = []( 42 | ULONG NotificationReason, 43 | PLDR_DLL_NOTIFICATION_DATA NotificationData, 44 | PVOID context 45 | ) { 46 | // Only interested in load events 47 | if (NotificationReason != LDR_DLL_NOTIFICATION_REASON_LOADED) { 48 | return; 49 | } 50 | 51 | const auto base_dll_name = util::to_string(NotificationData->Loaded.BaseDllName->Buffer); 52 | const auto full_dll_name = util::to_string(NotificationData->Loaded.FullDllName->Buffer); 53 | 54 | auto* const data = static_cast(context); 55 | 56 | for (const auto& library_name: data->target_library_names) { 57 | if (library_name + ".dll" < equals > base_dll_name) { 58 | LOG_DEBUG("Library '{}' has been loaded", library_name) 59 | 60 | auto* const loaded_module = win_util::get_module_handle(full_dll_name.c_str()); 61 | 62 | data->callback(loaded_module, library_name); 63 | } 64 | } 65 | 66 | // Do not delete data since it is re-used 67 | // delete data; 68 | }; 69 | 70 | auto* const context = new CallbackData{ 71 | .target_library_names = target_library_names, 72 | .callback = callback, 73 | }; 74 | 75 | static const auto LdrRegisterDllNotification = reinterpret_cast<_LdrRegisterDllNotification>( 76 | win_util::get_proc_address(win_util::get_module_handle("ntdll"), "LdrRegisterDllNotification") 77 | ); 78 | 79 | const auto status = LdrRegisterDllNotification(0, notification_listener, context, &cookie); 80 | 81 | if (status != STATUS_SUCCESS) { 82 | util::panic("Failed to register DLL listener. Status code: {}", status); 83 | } 84 | 85 | LOG_DEBUG("DLL monitor was successfully initialized") 86 | 87 | // Then check if the target dll is already loaded 88 | for (const auto& library_name: target_library_names) { 89 | try { 90 | auto* const original_library = win_util::get_module_handle_or_throw(library_name.c_str()); 91 | 92 | LOG_DEBUG("Library is already loaded: '{}'", library_name) 93 | 94 | callback(original_library, library_name); 95 | } catch (const std::exception& ex) {} 96 | } 97 | } 98 | 99 | KOALABOX_API(void) shutdown_listener() { 100 | static const auto LdrUnregisterDllNotification = reinterpret_cast<_LdrUnregisterDllNotification>( 101 | win_util::get_proc_address(win_util::get_module_handle("ntdll"), "LdrUnregisterDllNotification") 102 | ); 103 | 104 | LdrUnregisterDllNotification(cookie); 105 | cookie = nullptr; 106 | 107 | LOG_DEBUG("DLL monitor was successfully shut down") 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/koalabox/globals.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace koalabox::globals { 5 | 6 | namespace { 7 | Mutex mutex; 8 | bool initialized = false; 9 | 10 | HMODULE self_handle = nullptr; 11 | String project_name = ""; 12 | 13 | void validate_initialization() { 14 | const MutexLockGuard lock(mutex); 15 | 16 | if (not initialized) { 17 | util::panic("Koalabox globals are not initialized."); 18 | } 19 | } 20 | } 21 | 22 | KOALABOX_API(HMODULE) get_self_handle() { 23 | validate_initialization(); 24 | 25 | return self_handle; 26 | } 27 | 28 | KOALABOX_API(String) get_project_name(bool validate) { 29 | // Avoid recursion 30 | if (validate) { 31 | validate_initialization(); 32 | } 33 | 34 | return project_name; 35 | } 36 | 37 | KOALABOX_API(void) init_globals(HMODULE handle, String name) { 38 | const MutexLockGuard lock(mutex); 39 | 40 | self_handle = handle; 41 | project_name = name; 42 | 43 | initialized = true; 44 | 45 | DisableThreadLibraryCalls(self_handle); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/koalabox/hook.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace koalabox::hook { 10 | 11 | class PolyhookLogger : public PLH::Logger { 12 | bool print_info; 13 | public: 14 | explicit PolyhookLogger(bool print_info) : print_info(print_info) {}; 15 | 16 | void log_impl(const String& msg, PLH::ErrorLevel level) const { 17 | if (level == PLH::ErrorLevel::INFO && print_info) { 18 | LOG_DEBUG("[Polyhook] {}", msg) 19 | } else if (level == PLH::ErrorLevel::WARN) { 20 | LOG_WARN("[Polyhook] {}", msg) 21 | } else if (level == PLH::ErrorLevel::SEV) { 22 | LOG_ERROR("[Polyhook] {}", msg) 23 | } 24 | } 25 | 26 | void log(const String& msg, PLH::ErrorLevel level) override { 27 | log_impl(msg, level); 28 | } 29 | }; 30 | 31 | Vector hooks; // NOLINT(cert-err58-cpp) 32 | Map address_map; // NOLINT(cert-err58-cpp) 33 | 34 | KOALABOX_API(void) detour_or_throw( 35 | const uintptr_t address, 36 | const String& function_name, 37 | const uintptr_t callback_function 38 | ) { 39 | if (address == callback_function) { 40 | LOG_DEBUG("Function '{}' is already hooked. Skipping detour.", function_name) 41 | } 42 | 43 | LOG_DEBUG("Hooking '{}' at {} via Detour", function_name, (void*) address) 44 | 45 | uint64_t trampoline = 0; 46 | 47 | auto* const detour = new PLH::NatDetour(address, callback_function, &trampoline); 48 | 49 | #ifdef _WIN64 50 | detour->setDetourScheme(PLH::x64Detour::ALL); 51 | #endif 52 | if (detour->hook() || trampoline == 0) { 53 | hooks.push_back(detour); 54 | address_map[function_name] = trampoline; 55 | } else { 56 | throw util::exception("Failed to hook function: {}", function_name); 57 | } 58 | } 59 | 60 | KOALABOX_API(void) detour_or_throw( 61 | const HMODULE& module_handle, 62 | const String& function_name, 63 | const uintptr_t callback_function 64 | ) { 65 | const auto address = reinterpret_cast( 66 | win_util::get_proc_address_or_throw(module_handle, function_name.c_str()) 67 | ); 68 | 69 | detour_or_throw(address, function_name, callback_function); 70 | } 71 | 72 | KOALABOX_API(void) detour_or_warn( 73 | const uintptr_t address, 74 | const String& function_name, 75 | const uintptr_t callback_function 76 | ) { 77 | try { 78 | hook::detour_or_throw(address, function_name, callback_function); 79 | } catch (const Exception& ex) { 80 | LOG_WARN("Detour error: {}", ex.what()) 81 | } 82 | } 83 | 84 | KOALABOX_API(void) detour_or_warn( 85 | const HMODULE& module_handle, 86 | const String& function_name, 87 | const uintptr_t callback_function 88 | ) { 89 | try { 90 | hook::detour_or_throw(module_handle, function_name, callback_function); 91 | } catch (const Exception& ex) { 92 | LOG_WARN("Detour error: {}", ex.what()) 93 | } 94 | } 95 | 96 | KOALABOX_API(void) detour( 97 | const uintptr_t address, 98 | const String& function_name, 99 | const uintptr_t callback_function 100 | ) { 101 | try { 102 | detour_or_throw(address, function_name, callback_function); 103 | } catch (const Exception& ex) { 104 | util::panic("Failed to hook function {} via Detour: {}", function_name, ex.what()); 105 | } 106 | } 107 | 108 | KOALABOX_API(void) detour( 109 | const HMODULE& module_handle, 110 | const String& function_name, 111 | const uintptr_t callback_function 112 | ) { 113 | try { 114 | detour_or_throw(module_handle, function_name, callback_function); 115 | } catch (const Exception& ex) { 116 | util::panic("Failed to hook function {} via Detour: {}", function_name, ex.what()); 117 | } 118 | } 119 | 120 | KOALABOX_API(void) eat_hook_or_throw( 121 | const HMODULE& module_handle, 122 | const String& function_name, 123 | uintptr_t callback_function 124 | ) { 125 | LOG_DEBUG("Hooking '{}' via EAT", function_name) 126 | 127 | uint64_t orig_function_address = 0; 128 | auto* const eat_hook = new PLH::EatHook( 129 | function_name, 130 | module_handle, 131 | callback_function, 132 | &orig_function_address 133 | ); 134 | 135 | if (eat_hook->hook()) { 136 | address_map[function_name] = orig_function_address; 137 | 138 | hooks.push_back(eat_hook); 139 | } else { 140 | delete eat_hook; 141 | 142 | throw util::exception("Failed to hook function: '{}'", function_name); 143 | } 144 | } 145 | 146 | KOALABOX_API(void) eat_hook_or_warn( 147 | const HMODULE& module_handle, 148 | const String& function_name, 149 | uintptr_t callback_function 150 | ) { 151 | try { 152 | hook::eat_hook_or_throw(module_handle, function_name, callback_function); 153 | } catch (const Exception& ex) { 154 | LOG_WARN("Detour error: {}", ex.what()) 155 | } 156 | } 157 | 158 | KOALABOX_API(void) swap_virtual_func_or_throw( 159 | const void* instance, 160 | const String& function_name, 161 | const int ordinal, 162 | uintptr_t callback_function 163 | ) { 164 | if (instance) { 165 | const auto* vtable = *(uintptr_t**) instance; 166 | const auto target_func = vtable[ordinal]; 167 | 168 | if (target_func == callback_function) { 169 | LOG_DEBUG("Function '{}' is already hooked. Skipping virtual function swap", function_name) 170 | return; 171 | } 172 | } 173 | 174 | LOG_DEBUG( 175 | "Hooking '{}' at [[{}]+0x{:X}] via virtual function swap", 176 | function_name, instance, ordinal * sizeof(void*) 177 | ) 178 | 179 | const PLH::VFuncMap redirect = { 180 | {ordinal, callback_function}, 181 | }; 182 | 183 | PLH::VFuncMap original_functions; 184 | auto* const swap = new PLH::VFuncSwapHook((char*) instance, redirect, &original_functions); 185 | 186 | if (swap->hook()) { 187 | address_map[function_name] = original_functions[ordinal]; 188 | 189 | hooks.push_back(swap); 190 | } else { 191 | throw util::exception("Failed to hook function: {}", function_name); 192 | } 193 | } 194 | 195 | KOALABOX_API(void) swap_virtual_func( 196 | const void* instance, 197 | const String& function_name, 198 | const int ordinal, 199 | uintptr_t callback_function 200 | ) { 201 | try { 202 | swap_virtual_func_or_throw(instance, function_name, ordinal, callback_function); 203 | } catch (const Exception& ex) { 204 | util::panic("Failed to hook function {} via virtual function swap: {}", function_name, ex.what()); 205 | } 206 | } 207 | 208 | KOALABOX_API(uintptr_t) get_original_function(const HMODULE& library, const String& function_name) { 209 | return reinterpret_cast( 210 | win_util::get_proc_address(library, function_name.c_str()) 211 | ); 212 | } 213 | 214 | KOALABOX_API(uintptr_t) get_original_hooked_function(const String& function_name) { 215 | if (not address_map.contains(function_name)) { 216 | util::panic("Address map does not contain function: {}", function_name); 217 | } 218 | 219 | return address_map.at(function_name); 220 | } 221 | 222 | KOALABOX_API(void) init(bool print_info) { 223 | LOG_DEBUG("Hooking initialization") 224 | 225 | // Initialize polyhook logger 226 | auto polyhook_logger = std::make_shared(print_info); 227 | PLH::Log::registerLogger(polyhook_logger); 228 | } 229 | 230 | KOALABOX_API(bool) is_hook_mode(const HMODULE& self_module, const String& orig_library_name) { 231 | const auto module_path = win_util::get_module_file_name(self_module); 232 | 233 | const auto self_name = Path(module_path).stem().string(); 234 | 235 | return self_name < not_equals > orig_library_name; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/koalabox/http_client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | namespace koalabox::http_client { 8 | 9 | void validate_ok_response(const cpr::Response& res) { 10 | if (res.status_code != cpr::status::HTTP_OK) { 11 | throw util::exception( 12 | "Status code: {}, Error code: {},\nResponse headers:\n{}\nBody:\n{}", 13 | res.status_code, (int) res.error.code, res.raw_header, res.text 14 | ); 15 | } 16 | } 17 | 18 | KOALABOX_API(Json) get_json(const String& url) { 19 | LOG_DEBUG("GET {}", url) 20 | 21 | const auto res = cpr::Get(cpr::Url{ url }); 22 | 23 | validate_ok_response(res); 24 | 25 | LOG_TRACE("Response text: \n{}", res.text) 26 | 27 | return Json::parse(res.text); 28 | } 29 | 30 | KOALABOX_API(Json) post_json(const String& url, Json payload) { 31 | LOG_DEBUG("POST {}", url) 32 | 33 | const auto res = cpr::Post( 34 | cpr::Url{ url }, 35 | cpr::Header{{ "content-type", "application/json" }}, 36 | cpr::Body{ payload.dump() } 37 | ); 38 | 39 | validate_ok_response(res); 40 | 41 | LOG_TRACE("Response text: \n{}", res.text) 42 | 43 | return Json::parse(res.text); 44 | } 45 | 46 | KOALABOX_API(String) head_etag(const String& url) { 47 | const auto res = cpr::Head(cpr::Url{ url }); 48 | 49 | validate_ok_response(res); 50 | 51 | if (res.header.find("etag") != res.header.end()) { 52 | const auto etag = res.header.at("etag"); 53 | LOG_TRACE(R"(Etag for url "{}" = "{}")", url, etag) 54 | return etag; 55 | } 56 | 57 | LOG_TRACE(R"(No etag found for url "{}")", url) 58 | 59 | return ""; 60 | } 61 | 62 | KOALABOX_API(String) download_file(const String& url, const Path& destination) { 63 | LOG_DEBUG(R"(Downloading "{}" to "{}")", url, destination.string()) 64 | 65 | std::ofstream of(destination, std::ios::binary); 66 | cpr::Response res = cpr::Download(of, cpr::Url{ url }); 67 | 68 | validate_ok_response(res); 69 | 70 | LOG_DEBUG("Download complete ({} bytes)", res.downloaded_bytes) 71 | 72 | if (res.header.find("etag") != res.header.end()) { 73 | return res.header.at("etag"); 74 | } 75 | 76 | return ""; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/koalabox/io.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace koalabox::io { 12 | 13 | String read_file(const Path& file_path) { 14 | std::ifstream input_stream(file_path); 15 | input_stream.exceptions(std::ifstream::failbit | std::ifstream::badbit); 16 | 17 | try { 18 | return { std::istreambuf_iterator{ input_stream }, {}}; 19 | } catch (const std::system_error& e) { 20 | const auto& code = e.code(); 21 | throw std::runtime_error( 22 | fmt::format("Input file stream error code: {}, message: {}", code.value(), 23 | code.message()) 24 | ); 25 | } 26 | } 27 | 28 | bool write_file(const Path& file_path, const String& contents) noexcept { 29 | try { 30 | if (!std::filesystem::exists(file_path)) { 31 | std::filesystem::create_directories(file_path.parent_path()); 32 | } 33 | 34 | std::ofstream output_stream(file_path); 35 | if (output_stream.good()) { 36 | output_stream << contents; 37 | 38 | LOG_DEBUG(R"(Writing file to disk: "{}")", file_path.string()) 39 | return true; 40 | } 41 | 42 | LOG_ERROR( 43 | R"(Error opening output stream: "{}". Flags: {})", 44 | file_path.string(), output_stream.flags() 45 | ) 46 | return false; 47 | } catch (const Exception& e) { 48 | LOG_ERROR("Unexpected exception caught: {}", e.what()) 49 | return false; 50 | } 51 | } 52 | 53 | bool unzip_file( 54 | const Path& source_zip, 55 | const String& target_file, 56 | const Path& destination_dir 57 | ) noexcept { 58 | try { 59 | // open the zip archive for reading 60 | DECLARE_STRUCT(mz_zip_archive, archive); 61 | if (!mz_zip_reader_init_file(&archive, source_zip.string().c_str(), 0)) { 62 | throw KException("Error opening archive"); 63 | } 64 | 65 | // get the index of the file in the archive 66 | mz_uint file_index = mz_zip_reader_locate_file( 67 | &archive, 68 | target_file.c_str(), 69 | nullptr, 70 | 0 71 | ); 72 | 73 | if (file_index == -1) { 74 | mz_zip_reader_end(&archive); 75 | throw KException("File not found in archive"); 76 | } 77 | 78 | // get the uncompressed size of the file 79 | mz_zip_archive_file_stat file_stat; 80 | if (!mz_zip_reader_file_stat(&archive, file_index, &file_stat)) { 81 | mz_zip_reader_end(&archive); 82 | throw KException("Error getting file stats"); 83 | } 84 | 85 | // allocate a buffer to hold the uncompressed file data 86 | void* file_data = malloc(file_stat.m_uncomp_size); 87 | if (!file_data) { 88 | // error allocating buffer 89 | mz_zip_reader_end(&archive); 90 | throw KException("Error allocating buffer"); 91 | } 92 | 93 | // read the file data into the buffer 94 | if (!mz_zip_reader_extract_to_mem( 95 | &archive, 96 | file_index, 97 | file_data, 98 | file_stat.m_uncomp_size, 99 | 0 100 | )) { 101 | free(file_data); 102 | mz_zip_reader_end(&archive); 103 | throw KException("Error extracting file"); 104 | } 105 | 106 | // write the file data to disk 107 | const auto destination_path = destination_dir / Path(target_file).filename(); 108 | FILE* fp = fopen(destination_path.string().c_str(), "wb"); 109 | if (!fp) { 110 | // error creating file 111 | free(file_data); 112 | mz_zip_reader_end(&archive); 113 | throw KException("Error creating file"); 114 | } 115 | fwrite(file_data, 1, file_stat.m_uncomp_size, fp); 116 | fclose(fp); 117 | 118 | // clean up 119 | free(file_data); 120 | mz_zip_reader_end(&archive); 121 | 122 | return true; 123 | } catch (const Exception& e) { 124 | LOG_ERROR( 125 | R"(Error unzipping file "{}" from "{}" to "{}": {})", 126 | target_file, source_zip.string(), destination_dir.string(), e.what() 127 | ) 128 | 129 | return false; 130 | } 131 | } 132 | 133 | // Source: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect#example-code 134 | bool is_local_port_in_use(int port) { 135 | //---------------------- 136 | // Initialize Winsock 137 | WSADATA wsaData; 138 | int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); 139 | if (iResult != NO_ERROR) { 140 | // LOG_ERROR("WSAStartup error: {}", iResult) 141 | return false; 142 | } 143 | 144 | //---------------------- 145 | // Create a SOCKET for connecting to server 146 | SOCKET ConnectSocket; 147 | ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 148 | if (ConnectSocket == INVALID_SOCKET) { 149 | // LOG_ERROR("socket(...) error: {}", WSAGetLastError()) 150 | WSACleanup(); 151 | return false; 152 | } 153 | 154 | //---------------------- 155 | // The sockaddr_in structure specifies the address family, 156 | // IP address, and port of the server to be connected to. 157 | DECLARE_STRUCT(sockaddr_in, client_service); 158 | client_service.sin_family = AF_INET; 159 | client_service.sin_port = htons(port); 160 | iResult = InetPton(AF_INET, L"127.0.0.1", &client_service.sin_addr.s_addr); 161 | if (iResult != 1) { 162 | // LOG_ERROR("InetPton Error: {}", WSAGetLastError()) 163 | return false; 164 | } 165 | 166 | const auto close_socket = [&]() { 167 | iResult = closesocket(ConnectSocket); 168 | 169 | /*if (iResult == SOCKET_ERROR) { 170 | LOG_ERROR("closesocket(...) error: {}", WSAGetLastError()) 171 | }*/ 172 | 173 | WSACleanup(); 174 | }; 175 | 176 | //---------------------- 177 | // Connect to server. 178 | iResult = connect(ConnectSocket, (SOCKADDR*) &client_service, sizeof(client_service)); 179 | if (iResult == SOCKET_ERROR) { 180 | // LOG_ERROR("connect(...) error: {}", WSAGetLastError()) 181 | close_socket(); 182 | return false; 183 | } 184 | 185 | // If program has reached here, it means that the port was open 186 | close_socket(); 187 | 188 | return true; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/koalabox/ipc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace koalabox::ipc { 7 | 8 | constexpr auto BUFFER_SIZE = 32 * 1024; // 32Kb 9 | 10 | // Source: https://learn.microsoft.com/en-us/windows/win32/ipc/multithreaded-pipe-server 11 | KOALABOX_API(void) init_pipe_server( 12 | const String& pipe_id, 13 | const Function& callback 14 | ) { 15 | const auto pipe_name = R"(\\.\pipe\)" + pipe_id; 16 | const auto pipe_name_wstr = koalabox::util::to_wstring(pipe_name); 17 | 18 | while (true) { 19 | LOG_DEBUG("{} -> Awaiting client connections on '{}'", __func__, pipe_name) 20 | 21 | HANDLE hPipe = CreateNamedPipe( 22 | pipe_name_wstr.c_str(), // pipe name 23 | PIPE_ACCESS_DUPLEX, // read/write access 24 | PIPE_TYPE_MESSAGE | // message type pipe 25 | PIPE_READMODE_MESSAGE | // message-read mode 26 | PIPE_WAIT, // blocking mode 27 | PIPE_UNLIMITED_INSTANCES, // max. instances 28 | BUFFER_SIZE, // output buffer size 29 | BUFFER_SIZE, // input buffer size 30 | 0, // client time-out 31 | nullptr // default security attribute 32 | ); 33 | 34 | if (hPipe == INVALID_HANDLE_VALUE) { 35 | LOG_ERROR( 36 | "{} -> Error creating a named pipe. Last error: {}", 37 | __func__, koalabox::win_util::get_last_error() 38 | ) 39 | 40 | break; 41 | } 42 | 43 | const auto connected = ConnectNamedPipe(hPipe, nullptr) || (::GetLastError() == ERROR_PIPE_CONNECTED); 44 | if (!connected) { 45 | // The client could not connect, so close the pipe. 46 | CloseHandle(hPipe); 47 | break; 48 | } 49 | 50 | try { 51 | LOG_DEBUG("{} -> Received client connection", __func__) 52 | 53 | // Loop until done reading 54 | char request_buffer[BUFFER_SIZE] = {'\0'}; 55 | DWORD bytes_read = 0; 56 | const auto read_success = ReadFile( 57 | hPipe, // handle to pipe 58 | request_buffer, // buffer to receive data 59 | BUFFER_SIZE, // size of buffer 60 | &bytes_read, // number of bytes read 61 | nullptr // not overlapped I/O 62 | ); 63 | 64 | if (!read_success || bytes_read == 0) { 65 | if (GetLastError() == ERROR_BROKEN_PIPE) { 66 | LOG_ERROR("{} -> Client disconnected", __func__) 67 | } else { 68 | LOG_ERROR("{} -> ReadFile error: {}", __func__, koalabox::win_util::get_last_error()) 69 | } 70 | return; 71 | } 72 | 73 | Response response; 74 | try { 75 | const auto json = Json::parse(request_buffer); 76 | 77 | LOG_DEBUG("{} -> Parsed request json: \n{}", __func__, json.dump(2)) 78 | 79 | const auto request = json.get(); 80 | 81 | response = callback(request); 82 | } catch (const Exception& e) { 83 | LOG_ERROR("{} -> Error processing message: {}", __func__, e.what()) 84 | 85 | response.success = false; 86 | response.data["error_message"] = e.what(); 87 | } 88 | 89 | const auto response_str = Json(response).dump(2); 90 | 91 | LOG_DEBUG("{} -> Response json: \n{}", __func__, response_str) 92 | 93 | // Write the reply to the pipe. 94 | DWORD bytes_written = 0; 95 | const auto write_success = WriteFile( 96 | hPipe, // handle to pipe 97 | response_str.c_str(),// buffer to write from 98 | response_str.size(), // number of bytes to write 99 | &bytes_written, // number of bytes written 100 | nullptr // not overlapped I/O 101 | ); 102 | 103 | if (!write_success || response_str.size() != bytes_written) { 104 | LOG_ERROR( 105 | "{} -> Error writing file. Last error: {}", 106 | __func__, koalabox::win_util::get_last_error() 107 | ) 108 | return; 109 | } 110 | 111 | // Flush the pipe to allow the client to read the pipe's contents 112 | // before disconnecting. Then disconnect the pipe, and close the 113 | // handle to this pipe instance. 114 | 115 | FlushFileBuffers(hPipe); 116 | DisconnectNamedPipe(hPipe); 117 | CloseHandle(hPipe); 118 | 119 | LOG_DEBUG("{} -> Finished processing request", __func__) 120 | } catch (const Exception& e) { 121 | LOG_ERROR("Pipe server error processing client connection: {}", e.what()) 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/koalabox/loader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace koalabox::loader { 8 | 9 | KOALABOX_API(Path) get_module_dir(const HMODULE& handle) { 10 | const auto file_name = win_util::get_module_file_name(handle); 11 | 12 | const auto module_path = Path(util::to_wstring(file_name)); 13 | 14 | return module_path.parent_path(); 15 | } 16 | 17 | /** 18 | * Key is undecorated name, value is decorated name, if `undecorate` is set 19 | */ 20 | KOALABOX_API(Map) get_export_map(const HMODULE& library, bool undecorate) { 21 | // Adapted from: https://github.com/mborne/dll2def/blob/master/dll2def.cpp 22 | 23 | auto exported_functions = Map(); 24 | 25 | auto* dos_header = reinterpret_cast(library); 26 | 27 | if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) { 28 | util::panic("e_magic != IMAGE_DOS_SIGNATURE"); 29 | } 30 | 31 | auto* header = reinterpret_cast((BYTE*) library + dos_header->e_lfanew ); 32 | 33 | if (header->Signature != IMAGE_NT_SIGNATURE) { 34 | util::panic("header->Signature != IMAGE_NT_SIGNATURE"); 35 | } 36 | 37 | if (header->OptionalHeader.NumberOfRvaAndSizes <= 0) { 38 | util::panic("header->OptionalHeader.NumberOfRvaAndSizes <= 0"); 39 | } 40 | 41 | const auto& data_dir = header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; 42 | auto* exports = reinterpret_cast((BYTE*) library + data_dir.VirtualAddress); 43 | 44 | PVOID names = (BYTE*) library + exports->AddressOfNames; 45 | 46 | // Iterate over the names and add them to the vector 47 | for (unsigned int i = 0; i < exports->NumberOfNames; i++) { 48 | String exported_name = (char*) library + ((DWORD*) names)[i]; 49 | 50 | if (undecorate) { 51 | String undecorated_function = exported_name; // fallback value 52 | 53 | // Extract function name from decorated name 54 | static const std::regex expression(R"((?:^_)?(\w+)(?:@\d+$)?)"); 55 | 56 | std::smatch matches; 57 | if (std::regex_match(exported_name, matches, expression)) { 58 | if (matches.size() == 2) { 59 | undecorated_function = matches[1]; 60 | } else { 61 | LOG_WARN("Exported function regex size != 2: {}", exported_name) 62 | } 63 | } else { 64 | LOG_WARN("Exported function regex failed: {}", exported_name) 65 | } 66 | 67 | exported_functions.insert({undecorated_function, exported_name}); 68 | } else { 69 | exported_functions.insert({exported_name, exported_name}); 70 | } 71 | } 72 | 73 | return exported_functions; 74 | } 75 | 76 | KOALABOX_API(String) get_decorated_function(const HMODULE& library, const String& function_name) { 77 | #ifdef _WIN64 78 | SUPPRESS_UNUSED(library) 79 | return function_name; 80 | #else 81 | static Map> undecorated_function_maps; 82 | 83 | if (not undecorated_function_maps.contains(library)) { 84 | undecorated_function_maps[library] = get_export_map(library, true); 85 | } 86 | 87 | return undecorated_function_maps[library][function_name]; 88 | #endif 89 | } 90 | 91 | KOALABOX_API(HMODULE) load_original_library(const Path& self_path, const String& orig_library_name) { 92 | const auto original_module_path = self_path / (orig_library_name + "_o.dll"); 93 | 94 | auto* const original_module = win_util::load_library(original_module_path); 95 | 96 | LOG_INFO("📚 Loaded original library from: '{}'", original_module_path.string()) 97 | 98 | return original_module; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/koalabox/logger.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace koalabox::logger { 13 | 14 | std::shared_ptr instance = spdlog::null_logger_mt("null"); // NOLINT(cert-err58-cpp) 15 | 16 | /** 17 | * A custom implementation of a file sink which sanitizes messages before outputting them. 18 | */ 19 | class SanitizedFileSink : public spdlog::sinks::base_sink { 20 | public: 21 | const spdlog::filename_t& filename() const { 22 | return file_helper_.filename(); 23 | } 24 | 25 | explicit SanitizedFileSink(const spdlog::filename_t& filename) { 26 | file_helper_.open(filename, true); 27 | } 28 | 29 | protected: 30 | void sink_it_(const spdlog::details::log_msg& msg) override { 31 | // Replace username with environment variable 32 | static const std::regex username_regex(R"(\w:[/\\]Users[/\\]([^/\\]+))", std::regex_constants::icase); 33 | 34 | String payload(msg.payload.data(), msg.payload.size()); 35 | std::smatch matches; 36 | if (std::regex_search(payload, matches, username_regex)) { 37 | payload.replace(matches[1].first, matches[1].second, "%USERNAME%"); 38 | } 39 | 40 | const spdlog::details::log_msg sanitized_log_msg(msg.time, msg.source, msg.logger_name, msg.level, payload); 41 | spdlog::memory_buf_t formatted; 42 | base_sink::formatter_->format(sanitized_log_msg, formatted); 43 | 44 | file_helper_.write(formatted); 45 | } 46 | 47 | void flush_() override { 48 | file_helper_.flush(); 49 | }; 50 | 51 | private: 52 | spdlog::details::file_helper file_helper_; 53 | }; 54 | 55 | class EmojiFormatterFlag : public spdlog::custom_flag_formatter { 56 | public: 57 | void format(const spdlog::details::log_msg& log_msg, const std::tm&, spdlog::memory_buf_t& dest) override { 58 | String emoji; 59 | switch (log_msg.level) { 60 | case spdlog::level::critical: 61 | emoji = "💥"; 62 | break; 63 | case spdlog::level::err: 64 | emoji = "🟥"; 65 | break; 66 | case spdlog::level::warn: 67 | emoji = "🟨"; 68 | break; 69 | case spdlog::level::info: 70 | emoji = "🟩"; 71 | break; 72 | case spdlog::level::debug: 73 | emoji = "⬛"; 74 | break; 75 | case spdlog::level::trace: 76 | emoji = "🟦"; 77 | break; 78 | default: 79 | emoji = " "; 80 | break; 81 | } 82 | 83 | dest.append(emoji.data(), emoji.data() + emoji.size()); 84 | } 85 | 86 | [[nodiscard]] 87 | std::unique_ptr clone() const override { 88 | return spdlog::details::make_unique(); 89 | } 90 | }; 91 | 92 | /** 93 | * @param path It is the responsibility of the caller to ensure that all directories in the path exist. 94 | */ 95 | KOALABOX_API(void) init_file_logger(const Path& path) { 96 | instance = spdlog::create_async("async", path.string()); 97 | 98 | auto formatter = std::make_unique(); 99 | formatter->add_flag('*'); 100 | formatter->set_pattern("%*│ %H:%M:%S.%e │%v"); 101 | 102 | instance->set_formatter(std::move(formatter)); 103 | instance->set_level(spdlog::level::trace); 104 | instance->flush_on(spdlog::level::trace); 105 | } 106 | 107 | KOALABOX_API(void) init_file_logger() { 108 | init_file_logger(koalabox::paths::get_log_path()); 109 | } 110 | 111 | KOALABOX_API(String) get_filename(const char* full_path) { 112 | static Map results; 113 | 114 | if (not results.contains(full_path)) { 115 | results[full_path] = Path(full_path).filename().string(); 116 | } 117 | 118 | return results[full_path]; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/koalabox/ntapi.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Source: https://github.com/blaquee/dllnotif/blob/master/LdrDllNotification/ntapi.h 4 | 5 | typedef __success(return >= 0) LONG NTSTATUS; 6 | 7 | #ifndef NT_STATUS_OK 8 | #define NT_STATUS_OK 0 9 | #endif 10 | 11 | #define STATUS_SUCCESS ((NTSTATUS)0) 12 | #define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001) 13 | #define STATUS_PROCEDURE_NOT_FOUND ((NTSTATUS)0xC000007A) 14 | #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004) 15 | #define STATUS_NOT_FOUND ((NTSTATUS)0xC0000225) 16 | #define STATUS_THREAD_IS_TERMINATING ((NTSTATUS)0xc000004b) 17 | #define STATUS_NOT_SUPPORTED ((NTSTATUS)0xC00000BB) 18 | 19 | enum LDR_DLL_NOTIFICATION_REASON { 20 | LDR_DLL_NOTIFICATION_REASON_LOADED = 1, 21 | LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2, 22 | }; 23 | 24 | typedef struct tag_UNICODE_STRING { 25 | USHORT Length; 26 | USHORT MaximumLength; 27 | PWSTR Buffer; 28 | } __UNICODE_STRING, * PCUNICODE_STRING; // Removed * PUNICODE_STRING to avoid conflicts with PLH's PEB.hpp 29 | 30 | typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA { 31 | ULONG Flags; //Reserved. 32 | PCUNICODE_STRING FullDllName; //The full path name of the DLL module. 33 | PCUNICODE_STRING BaseDllName; //The base file name of the DLL module. 34 | PVOID DllBase; //A pointer to the base address for the DLL in memory. 35 | ULONG SizeOfImage; //The size of the DLL image, in bytes. 36 | } LDR_DLL_LOADED_NOTIFICATION_DATA, * PLDR_DLL_LOADED_NOTIFICATION_DATA; 37 | 38 | typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA { 39 | ULONG Flags; //Reserved. 40 | PCUNICODE_STRING FullDllName; //The full path name of the DLL module. 41 | PCUNICODE_STRING BaseDllName; //The base file name of the DLL module. 42 | PVOID DllBase; //A pointer to the base address for the DLL in memory. 43 | ULONG SizeOfImage; //The size of the DLL image, in bytes. 44 | } LDR_DLL_UNLOADED_NOTIFICATION_DATA, * PLDR_DLL_UNLOADED_NOTIFICATION_DATA; 45 | 46 | typedef union _LDR_DLL_NOTIFICATION_DATA { 47 | LDR_DLL_LOADED_NOTIFICATION_DATA Loaded; 48 | LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded; 49 | } LDR_DLL_NOTIFICATION_DATA, * PLDR_DLL_NOTIFICATION_DATA; 50 | typedef const LDR_DLL_NOTIFICATION_DATA* PCLDR_DLL_NOTIFICATION_DATA; 51 | 52 | typedef VOID(CALLBACK* PLDR_DLL_NOTIFICATION_FUNCTION)( 53 | _In_ ULONG NotificationReason, 54 | _In_ PLDR_DLL_NOTIFICATION_DATA NotificationData, 55 | _In_opt_ PVOID Context 56 | ); 57 | 58 | typedef NTSTATUS(NTAPI* _LdrRegisterDllNotification)( 59 | _In_ ULONG Flags, 60 | _In_ PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, 61 | _In_opt_ PVOID Context, 62 | _Out_ PVOID* cookie 63 | ); 64 | 65 | typedef NTSTATUS(NTAPI* _LdrUnregisterDllNotification)( 66 | _In_ PVOID cookie 67 | ); 68 | -------------------------------------------------------------------------------- /src/koalabox/patcher.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace koalabox::patcher { 9 | 10 | struct PatternMask { 11 | String binary_pattern; 12 | String mask; 13 | }; 14 | 15 | /** 16 | * Converts user-friendly hex pattern string into a byte array 17 | * and generates corresponding string mask 18 | */ 19 | PatternMask get_pattern_and_mask(String pattern) { 20 | // Remove whitespaces 21 | pattern = std::regex_replace(pattern, std::regex("\\s+"), ""); 22 | 23 | // Convert hex to binary 24 | std::stringstream patternStream; 25 | std::stringstream maskStream; 26 | for (size_t i = 0; i < pattern.length(); i += 2) { 27 | const std::string byteString = pattern.substr(i, 2); 28 | 29 | maskStream << (byteString == "??" ? '?' : 'x'); 30 | 31 | // Handle wildcards ourselves, rest goes to strtol 32 | patternStream << ( 33 | byteString == "??" ? '?' : (char) strtol(byteString.c_str(), nullptr, 16) 34 | ); 35 | } 36 | 37 | return {patternStream.str(), maskStream.str()}; 38 | } 39 | 40 | // Credit: superdoc1234 41 | // Source: https://www.unknowncheats.me/forum/1364641-post150.html 42 | uintptr_t find(const uintptr_t base_address, size_t mem_length, const char* pattern, const char* mask) { 43 | 44 | auto i_end = strlen(mask) - 1; 45 | 46 | auto DataCompare = [&](const char* data) -> bool { 47 | if (data[i_end] != pattern[i_end]) { 48 | return false; 49 | } 50 | 51 | for (size_t i = 0; i <= i_end; ++i) { 52 | if (mask[i] == 'x' && data[i] != pattern[i]) { 53 | return false; 54 | } 55 | } 56 | 57 | return true; 58 | }; 59 | 60 | 61 | for (size_t i = 0; i < mem_length - strlen(mask); ++i) { 62 | if (DataCompare(reinterpret_cast(base_address + i))) { 63 | return base_address + i; 64 | } 65 | } 66 | 67 | return 0; 68 | } 69 | 70 | // Credit: Rake 71 | // Source: https://guidedhacking.com/threads/external-internal-pattern-scanning-guide.14112/ 72 | uintptr_t scan_internal(uintptr_t ptr_memory, size_t length, String pattern) { 73 | const uintptr_t terminal_address = ptr_memory + length; 74 | 75 | uintptr_t match = 0; 76 | MEMORY_BASIC_INFORMATION mbi{}; 77 | 78 | auto [binaryPattern, mask] = get_pattern_and_mask(std::move(pattern)); 79 | 80 | auto current_region = ptr_memory; 81 | do { 82 | // Skip irrelevant code regions 83 | auto query_success = VirtualQuery((LPCVOID) current_region, &mbi, sizeof(mbi)); 84 | if (query_success && mbi.State == MEM_COMMIT && mbi.Protect != PAGE_NOACCESS) { 85 | LOG_TRACE( 86 | "current_region: {}, mbi.BaseAddress: {}, mbi.RegionSize: {}", 87 | (void*) current_region, mbi.BaseAddress, (void*) mbi.RegionSize 88 | ) 89 | 90 | const uintptr_t potential_end = current_region + mbi.RegionSize; 91 | const auto max_address = std::min(potential_end, terminal_address); 92 | const auto mem_length = max_address - current_region; 93 | match = find(current_region, mem_length, binaryPattern.c_str(), mask.c_str()); 94 | 95 | if (match) { 96 | break; 97 | } 98 | } 99 | 100 | current_region += mbi.RegionSize; 101 | } while (current_region < ptr_memory + length); 102 | 103 | return match; 104 | } 105 | 106 | KOALABOX_API(uintptr_t) find_pattern_address( 107 | const uintptr_t base_address, 108 | size_t scan_size, 109 | const String& name, 110 | const String& pattern 111 | ) { 112 | const auto t1 = std::chrono::high_resolution_clock::now(); 113 | const auto address = scan_internal(base_address, scan_size, pattern); 114 | const auto t2 = std::chrono::high_resolution_clock::now(); 115 | 116 | const double elapsed_time = std::chrono::duration(t2 - t1).count(); 117 | 118 | if (address) { 119 | LOG_DEBUG("'{}' address: {}. Search time: {:.2f} ms", name, (void*) address, elapsed_time) 120 | } else { 121 | LOG_ERROR("Failed to find address of '{}'. Search time: {:.2f} ms", name, elapsed_time) 122 | } 123 | 124 | return address; 125 | } 126 | 127 | KOALABOX_API(uintptr_t) find_pattern_address(const MODULEINFO& process_info, const String& name, 128 | const String& pattern) { 129 | return find_pattern_address( 130 | reinterpret_cast(process_info.lpBaseOfDll), 131 | process_info.SizeOfImage, 132 | name, 133 | pattern 134 | ); 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/koalabox/paths.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace koalabox::paths { 10 | 11 | namespace { 12 | String get_file_name(const String& suffix) { 13 | return koalabox::globals::get_project_name() + suffix; 14 | } 15 | } 16 | 17 | KOALABOX_API(Path) get_self_path() { 18 | static const auto self_handle = koalabox::globals::get_self_handle(); 19 | static const auto self_path = koalabox::loader::get_module_dir(self_handle); 20 | return self_path; 21 | } 22 | 23 | KOALABOX_API(Path) get_config_path() { 24 | static const auto path = get_self_path() / get_file_name(".config.json"); 25 | return path; 26 | } 27 | 28 | KOALABOX_API(Path) get_cache_path() { 29 | static const auto path = get_self_path() / get_file_name(".cache.json"); 30 | return path; 31 | } 32 | 33 | KOALABOX_API(Path) get_log_path() { 34 | static const auto path = get_self_path() / get_file_name(".log.log"); 35 | return path; 36 | } 37 | 38 | KOALABOX_API(Path) get_ca_key_path() { 39 | static const auto project_name = koalabox::globals::get_project_name(); 40 | static const auto path = get_self_path() / (project_name + ".ca.key"); 41 | return path; 42 | } 43 | 44 | KOALABOX_API(Path) get_ca_cert_path() { 45 | static const auto project_name = koalabox::globals::get_project_name(); 46 | static const auto path = get_self_path() / (project_name + ".ca.crt"); 47 | return path; 48 | } 49 | 50 | KOALABOX_API(Path) get_cache_dir() { 51 | static const auto path = get_self_path() / "cache"; 52 | create_directories(path); 53 | return path; 54 | } 55 | 56 | KOALABOX_API(Path) get_user_dir() { 57 | TCHAR buffer[MAX_PATH]; 58 | if (SHGetSpecialFolderPath(nullptr, buffer, CSIDL_PROFILE, FALSE)) { 59 | // Path retrieved successfully, so print it out 60 | return { buffer }; 61 | } 62 | 63 | throw KException("Error retrieving user directory: {}", GET_LAST_ERROR()); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/koalabox/util.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace koalabox::util { 7 | 8 | KOALABOX_API(void) error_box(const String& title, const String& message) { 9 | ::MessageBox( 10 | nullptr, 11 | to_wstring(message).c_str(), 12 | to_wstring(title).c_str(), 13 | MB_OK | MB_ICONERROR 14 | ); 15 | } 16 | 17 | [[noreturn]] KOALABOX_API(void) panic(String message) { 18 | const auto title = fmt::format("[{}] Panic!", globals::get_project_name(false)); 19 | 20 | const auto last_error = ::GetLastError(); 21 | if (last_error != 0) { 22 | message += fmt::format( 23 | "\n———————— Windows Last Error ————————\nCode: {}\nMessage: {}", 24 | last_error, win_util::format_message(last_error) 25 | ); 26 | } 27 | 28 | LOG_CRITICAL("{}", message) 29 | 30 | error_box(title, message); 31 | 32 | exit(static_cast(last_error)); 33 | } 34 | 35 | KOALABOX_API(String) to_string(const WideString& wstr) { 36 | if (wstr.empty()) { 37 | return {}; 38 | } 39 | 40 | const auto required_size = WideCharToMultiByte( 41 | CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), nullptr, 0, nullptr, nullptr 42 | ); 43 | 44 | String string(required_size, 0); 45 | WideCharToMultiByte( 46 | CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), string.data(), required_size, 47 | nullptr, nullptr 48 | ); 49 | 50 | return string; 51 | } 52 | 53 | KOALABOX_API(WideString) to_wstring(const String& str) { 54 | if (str.empty()) { 55 | return {}; 56 | } 57 | 58 | const auto required_size = MultiByteToWideChar( 59 | CP_UTF8, 0, str.data(), static_cast(str.size()), nullptr, 0 60 | ); 61 | 62 | WideString wstring(required_size, 0); 63 | MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.size()), wstring.data(), 64 | required_size); 65 | 66 | return wstring; 67 | } 68 | 69 | // Source: https://guidedhacking.com/threads/testing-if-pointer-is-invalid.13222/post-77709 70 | KOALABOX_API(bool) is_valid_pointer(const void* pointer) { 71 | const auto mbi_opt = win_util::virtual_query(pointer); 72 | 73 | if (mbi_opt) { 74 | const auto is_rwe = mbi_opt->Protect & ( 75 | PAGE_READONLY | 76 | PAGE_READWRITE | 77 | PAGE_WRITECOPY | 78 | PAGE_EXECUTE_READ | 79 | PAGE_EXECUTE_READWRITE | 80 | PAGE_EXECUTE_WRITECOPY 81 | ); 82 | 83 | const auto is_guarded = mbi_opt->Protect & ( 84 | PAGE_GUARD | 85 | PAGE_NOACCESS 86 | ); 87 | 88 | return is_rwe && !is_guarded; 89 | } 90 | 91 | return false; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/koalabox/win_util.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #pragma comment(lib, "Version.lib") 6 | 7 | /** 8 | * NOTE: It's important not to log anything in these functions, since logging might not have been 9 | * initialized yet. All errors must be reported via exceptions, which will be then displayed to user 10 | * via logs or message boxes depending on the circumstance. 11 | */ 12 | namespace koalabox::win_util { 13 | 14 | namespace { 15 | Vector get_file_version_info_or_throw(const HMODULE& module_handle) { 16 | const auto file_name = util::to_wstring(get_module_file_name_or_throw(module_handle)); 17 | 18 | DWORD version_handle = 0; 19 | const DWORD version_size = GetFileVersionInfoSize(file_name.c_str(), &version_handle); 20 | 21 | if (not version_size) { 22 | throw util::exception("Failed to GetFileVersionInfoSize. Error: {}", 23 | get_last_error()); 24 | } 25 | 26 | Vector version_data(version_size); 27 | 28 | if (not GetFileVersionInfo(file_name.c_str(), version_handle, version_size, 29 | version_data.data())) { 30 | throw util::exception("Failed to GetFileVersionInfo. Error: {}", get_last_error()); 31 | } 32 | 33 | return version_data; 34 | } 35 | } 36 | 37 | #define PANIC_ON_CATCH(FUNC, ...) \ 38 | try { \ 39 | return FUNC##_or_throw(__VA_ARGS__); \ 40 | } catch (const Exception& ex) { \ 41 | util::panic(ex.what()); \ 42 | } 43 | 44 | KOALABOX_API(PROCESS_INFORMATION) create_process( 45 | const String& app_name, 46 | const String& args, 47 | const Path& working_dir, 48 | bool show_window 49 | ) { 50 | DECLARE_STRUCT(PROCESS_INFORMATION, process_info); 51 | DECLARE_STRUCT(STARTUPINFO, startup_info); 52 | 53 | const auto cmd_line = app_name + " " + args; 54 | 55 | LOG_TRACE("Launching {}",cmd_line) 56 | 57 | const auto success = CreateProcess( 58 | NULL, 59 | WSTR(cmd_line).data(), 60 | NULL, 61 | NULL, 62 | NULL, 63 | show_window ? 0 : CREATE_NO_WINDOW, 64 | NULL, 65 | working_dir.wstring().c_str(), 66 | &startup_info, 67 | &process_info 68 | ) != 0; 69 | 70 | if (!success) { 71 | DWORD exit_code = 0; 72 | GetExitCodeProcess(process_info.hProcess, &exit_code); 73 | 74 | throw util::exception( 75 | R"(Error creating process "{}" with args "{}" at "{}". Last error: {}, Exit code: {})", 76 | app_name, args, working_dir.string(), get_last_error(), exit_code 77 | ); 78 | } 79 | 80 | WaitForInputIdle(process_info.hProcess, INFINITE); 81 | 82 | return process_info; 83 | } 84 | 85 | KOALABOX_API(String) format_message(const DWORD message_id) { 86 | TCHAR buffer[1024]; 87 | 88 | ::FormatMessage( 89 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 90 | nullptr, 91 | message_id, 92 | MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), 93 | buffer, 94 | sizeof(buffer) / sizeof(TCHAR), 95 | nullptr 96 | ); 97 | 98 | return util::to_string(buffer); 99 | } 100 | 101 | KOALABOX_API(String) get_last_error() { 102 | return fmt::format("0x{0:x}", ::GetLastError()); 103 | } 104 | 105 | KOALABOX_API(String) get_module_file_name_or_throw(const HMODULE& module_handle) { 106 | constexpr auto buffer_size = 1024; 107 | TCHAR buffer[buffer_size]; 108 | const auto length = ::GetModuleFileName(module_handle, buffer, buffer_size); 109 | 110 | return (length > 0 and length < buffer_size) 111 | ? util::to_string(buffer) 112 | : throw util::exception( 113 | "Failed to get a file name of the given module handle: {}. Length: {}", 114 | fmt::ptr(module_handle), length 115 | ); 116 | } 117 | 118 | KOALABOX_API(String) get_module_file_name(const HMODULE& module_handle) { 119 | PANIC_ON_CATCH(get_module_file_name, module_handle) 120 | } 121 | 122 | KOALABOX_API(HMODULE) get_module_handle_or_throw(LPCSTR module_name) { 123 | auto* const handle = module_name 124 | ? ::GetModuleHandle(util::to_wstring(module_name).c_str()) 125 | : ::GetModuleHandle(nullptr); 126 | 127 | return handle ? handle : throw util::exception( 128 | "Failed to get a handle of the module: '{}'", module_name 129 | ); 130 | } 131 | 132 | KOALABOX_API(HMODULE) get_module_handle(LPCSTR module_name) { 133 | PANIC_ON_CATCH(get_module_handle, module_name) 134 | } 135 | 136 | KOALABOX_API(MODULEINFO) get_module_info_or_throw(const HMODULE& module_handle) { 137 | MODULEINFO module_info = { nullptr }; 138 | const auto success = ::GetModuleInformation( 139 | GetCurrentProcess(), module_handle, &module_info, sizeof(module_info) 140 | ); 141 | 142 | return success ? module_info : throw util::exception( 143 | "Failed to get module info of the given module handle: {}", fmt::ptr(module_handle) 144 | ); 145 | } 146 | 147 | KOALABOX_API(MODULEINFO) get_module_info(const HMODULE& module_handle) { 148 | PANIC_ON_CATCH(get_module_info, module_handle) 149 | } 150 | 151 | KOALABOX_API(String) get_module_manifest(const HMODULE& module_handle) { 152 | struct Response { 153 | bool success = false; 154 | String manifest_or_error; 155 | }; 156 | 157 | // Return TRUE to continue enumeration or FALSE to stop enumeration. 158 | const auto callback = []( 159 | HMODULE hModule, 160 | LPCTSTR lpType, 161 | LPTSTR lpName, 162 | LONG_PTR lParam 163 | ) -> BOOL { 164 | const auto response = [&](bool success, String manifest_or_error) { 165 | try { 166 | auto* response = (Response*) lParam; 167 | 168 | if (!success) { 169 | manifest_or_error = fmt::format( 170 | "get_module_manifest_callback -> {}. Last error: {}", manifest_or_error, 171 | get_last_error() 172 | ); 173 | } 174 | 175 | response->success = success; 176 | response->manifest_or_error = manifest_or_error; 177 | 178 | return TRUE; 179 | } catch (const Exception& e) { 180 | LOG_ERROR("EnumResourceNames callback error: {}", e.what()) 181 | 182 | return FALSE; 183 | } 184 | }; 185 | 186 | HRSRC resource_handle = ::FindResource(hModule, lpName, lpType); 187 | if (resource_handle == nullptr) { 188 | return response(false, "FindResource returned null."); 189 | } 190 | 191 | const auto resource_size = SizeofResource(hModule, resource_handle); 192 | if (resource_size == 0) { 193 | return response(false, "SizeofResource returned 0."); 194 | } 195 | 196 | HGLOBAL resource_data_handle = LoadResource(hModule, resource_handle); 197 | if (resource_data_handle == nullptr) { 198 | return response(false, "LockResource returned null."); 199 | } 200 | 201 | const auto* resource_data = LockResource(resource_data_handle); 202 | if (resource_data == nullptr) { 203 | return response(false, "Resource data is null."); 204 | } 205 | 206 | return response(true, (char*) resource_data); 207 | }; 208 | 209 | Response response; 210 | if (not EnumResourceNames(module_handle, RT_MANIFEST, callback, (LONG_PTR) &response)) { 211 | throw util::exception("EnumResourceNames call error. Last error: {}", get_last_error()); 212 | } 213 | 214 | if (!response.success) { 215 | throw util::exception("{}", response.manifest_or_error); 216 | } 217 | 218 | return response.manifest_or_error; 219 | } 220 | 221 | KOALABOX_API(String) get_module_version_or_throw(const HMODULE& module_handle) { 222 | const auto version_data = get_file_version_info_or_throw(module_handle); 223 | 224 | UINT size = 0; 225 | VS_FIXEDFILEINFO* version_info = nullptr; 226 | if (not VerQueryValue(version_data.data(), TEXT("\\"), (VOID FAR* FAR*) &version_info, 227 | &size)) { 228 | throw util::exception("Failed to VerQueryValue. Error: {}", get_last_error()); 229 | } 230 | 231 | if (not size) { 232 | throw util::exception("Failed to VerQueryValue. Error: {}", get_last_error()); 233 | } 234 | 235 | if (version_info->dwSignature != 0xfeef04bd) { 236 | throw util::exception("VerQueryValue signature mismatch. Signature: 0x{0:x}", 237 | version_info->dwSignature); 238 | } 239 | 240 | return fmt::format( 241 | "{}.{}.{}.{}", 242 | (version_info->dwFileVersionMS >> 16) & 0xffff, 243 | (version_info->dwFileVersionMS >> 0) & 0xffff, 244 | (version_info->dwFileVersionLS >> 16) & 0xffff, 245 | (version_info->dwFileVersionLS >> 0) & 0xffff 246 | ); 247 | } 248 | 249 | KOALABOX_API(String) get_pe_section_data_or_throw( 250 | const HMODULE& module_handle, const String& section_name 251 | ) { 252 | auto* const dos_header = reinterpret_cast(module_handle); 253 | 254 | if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) { 255 | throw util::exception("Invalid DOS file"); 256 | } 257 | 258 | auto* const nt_header = (PIMAGE_NT_HEADERS) ((uint8_t*) module_handle + 259 | (dos_header->e_lfanew)); 260 | 261 | if (nt_header->Signature != IMAGE_NT_SIGNATURE) { 262 | throw util::exception("Invalid NT signature"); 263 | } 264 | 265 | auto* section = IMAGE_FIRST_SECTION(nt_header); 266 | for (int i = 0; i < nt_header->FileHeader.NumberOfSections; i++, section++) { 267 | auto name = String((char*) section->Name, 8); 268 | name = name.substr(0, name.find('\0')); // strip null padding 269 | 270 | if (name != section_name) { 271 | continue; 272 | } 273 | 274 | return { (char*) module_handle + section->PointerToRawData, section->SizeOfRawData }; 275 | } 276 | 277 | throw util::exception("Section '{}' not found", section_name); 278 | } 279 | 280 | KOALABOX_API(String) get_pe_section_data( 281 | const HMODULE& module_handle, const String& section_name 282 | ) { 283 | PANIC_ON_CATCH(get_pe_section_data, module_handle, section_name) 284 | } 285 | 286 | KOALABOX_API(FARPROC) get_proc_address_or_throw(const HMODULE& handle, LPCSTR procedure_name) { 287 | const auto address = ::GetProcAddress(handle, procedure_name); 288 | 289 | return address 290 | ? address 291 | : throw util::exception("Failed to get the address of the procedure: '{}'", 292 | procedure_name); 293 | } 294 | 295 | KOALABOX_API(FARPROC) get_proc_address(const HMODULE& handle, LPCSTR procedure_name) { 296 | PANIC_ON_CATCH(get_proc_address, handle, procedure_name) 297 | } 298 | 299 | KOALABOX_API(Path) get_system_directory_or_throw() { 300 | WCHAR path[MAX_PATH]; 301 | const auto result = GetSystemDirectory(path, MAX_PATH); 302 | 303 | if (result > MAX_PATH) { 304 | throw util::exception( 305 | "GetSystemDirectory path length ({}) is greater than MAX_PATH ({})", 306 | result, MAX_PATH 307 | ); 308 | } 309 | 310 | if (result == 0) { 311 | throw std::exception("Failed to get the path of the system directory"); 312 | } 313 | 314 | return std::filesystem::absolute(path); 315 | } 316 | 317 | KOALABOX_API(Path) get_system_directory() { 318 | PANIC_ON_CATCH(get_system_directory) 319 | } 320 | 321 | KOALABOX_API(void) free_library_or_throw(const HMODULE& handle) { 322 | if (not::FreeLibrary(handle)) { 323 | throw util::exception("Failed to free a library with the given module handle: {}", 324 | fmt::ptr(handle)); 325 | } 326 | } 327 | 328 | KOALABOX_API(bool) free_library(const HMODULE& handle, bool panic_on_fail) { 329 | try { 330 | free_library_or_throw(handle); 331 | return true; 332 | } catch (std::exception& ex) { 333 | if (panic_on_fail) { 334 | util::panic(ex.what()); 335 | } 336 | 337 | return false; 338 | } 339 | } 340 | 341 | KOALABOX_API(HMODULE) load_library_or_throw(const Path& module_path) { 342 | auto* const module_handle = ::LoadLibrary(module_path.wstring().c_str()); 343 | 344 | return module_handle 345 | ? module_handle 346 | : throw util::exception("Failed to load the module at path: '{}'", 347 | module_path.string()); 348 | } 349 | 350 | KOALABOX_API(HMODULE) load_library(const Path& module_path) { 351 | PANIC_ON_CATCH(load_library, module_path) 352 | } 353 | 354 | KOALABOX_API(void) register_application_restart() { 355 | const auto result = RegisterApplicationRestart(nullptr, 0); 356 | 357 | if (result != S_OK) { 358 | throw util::exception("Failed to register application restart. Result: {}", result); 359 | } 360 | } 361 | 362 | KOALABOX_API(std::optional) virtual_query(const void* pointer) { 363 | MEMORY_BASIC_INFORMATION mbi{}; 364 | 365 | if (::VirtualQuery(pointer, &mbi, sizeof(mbi))) { 366 | return mbi; 367 | } 368 | 369 | return std::nullopt; 370 | } 371 | 372 | KOALABOX_API(SIZE_T) write_process_memory_or_throw( 373 | const HANDLE& process, LPVOID address, LPCVOID buffer, SIZE_T size 374 | ) { 375 | SIZE_T bytes_written = 0; 376 | 377 | return WriteProcessMemory(process, address, buffer, size, &bytes_written) 378 | ? bytes_written 379 | : throw util::exception( 380 | "Failed to write process memory at address: '{}'", fmt::ptr(address) 381 | ); 382 | } 383 | 384 | KOALABOX_API(SIZE_T) write_process_memory( 385 | const HANDLE& process, LPVOID address, LPCVOID buffer, SIZE_T size 386 | ) { 387 | PANIC_ON_CATCH(write_process_memory, process, address, buffer, size) 388 | } 389 | 390 | } 391 | --------------------------------------------------------------------------------